cito-mcp 0.6.0 → 0.6.1
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/dist/client.js +8 -8
- package/dist/envelope.js +4 -4
- package/dist/install.js +15 -15
- package/dist/pacing.js +2 -2
- package/dist/scrub.js +58 -20
- package/dist/tools/combat.js +96 -96
- package/dist/tools/insight.js +36 -36
- package/dist/tools/leaderboard.js +4 -4
- package/dist/tools/live.js +41 -41
- package/dist/tools/match.js +25 -21
- package/dist/tools/meta.js +69 -69
- package/dist/tools/player.js +23 -23
- package/dist/tools/rankings.js +7 -7
- package/dist/tools/resolve.js +53 -53
- package/dist/tools/schedule.js +8 -8
- package/dist/tools/standings.js +23 -23
- package/dist/tools/team.js +88 -88
- package/dist/tools/tournaments.js +2 -2
- package/dist/tools/webhooks.js +2 -2
- package/package.json +51 -52
package/dist/tools/team.js
CHANGED
|
@@ -31,18 +31,18 @@ function numberOrNull(value) {
|
|
|
31
31
|
}
|
|
32
32
|
export const teamProfile = {
|
|
33
33
|
name: 'team_profile',
|
|
34
|
-
description: `Team/org card: identity, roster, recent matches, and form/trends/radar when available.
|
|
35
|
-
|
|
36
|
-
When to use:
|
|
37
|
-
- Team page / "who is on this roster?"
|
|
38
|
-
- Builder team screen sample
|
|
39
|
-
|
|
40
|
-
Prefer over: separate roster + matches + detail via call_api.
|
|
41
|
-
|
|
42
|
-
Do not use when: UFC fighters → player_profile; unknown name → resolve_entity first.
|
|
43
|
-
Dota may return partial roster (API gap). Prefer slug for lol/cod; teamId for cs2.
|
|
44
|
-
|
|
45
|
-
Parallel-safe: yes. API calls: 2–4.
|
|
34
|
+
description: `Team/org card: identity, roster, recent matches, and form/trends/radar when available.
|
|
35
|
+
|
|
36
|
+
When to use:
|
|
37
|
+
- Team page / "who is on this roster?"
|
|
38
|
+
- Builder team screen sample
|
|
39
|
+
|
|
40
|
+
Prefer over: separate roster + matches + detail via call_api.
|
|
41
|
+
|
|
42
|
+
Do not use when: UFC fighters → player_profile; unknown name → resolve_entity first.
|
|
43
|
+
Dota may return partial roster (API gap). Prefer slug for lol/cod; teamId for cs2.
|
|
44
|
+
|
|
45
|
+
Parallel-safe: yes. API calls: 2–4.
|
|
46
46
|
Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
47
47
|
inputSchema: {
|
|
48
48
|
type: 'object',
|
|
@@ -128,12 +128,12 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
128
128
|
const idOrSlug = teamId || slug;
|
|
129
129
|
const recentLimit = clampInt(args.recentLimit, 10, 1, 25);
|
|
130
130
|
const partial = [];
|
|
131
|
-
let
|
|
131
|
+
let apiCalls = 0;
|
|
132
132
|
let rateLimit = {};
|
|
133
133
|
let teamRaw = null;
|
|
134
134
|
if (game === 'lol') {
|
|
135
135
|
const res = await fetchJson(ctx, `/lol/teams/${encodeURIComponent(slug || idOrSlug)}`);
|
|
136
|
-
|
|
136
|
+
apiCalls += 1;
|
|
137
137
|
rateLimit = res.headers;
|
|
138
138
|
if (!res.ok) {
|
|
139
139
|
return errorEnvelope({
|
|
@@ -143,7 +143,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
143
143
|
source: 'team_profile',
|
|
144
144
|
requestId,
|
|
145
145
|
tookMs: Date.now() - started,
|
|
146
|
-
|
|
146
|
+
apiCalls,
|
|
147
147
|
httpStatus: res.status,
|
|
148
148
|
rateLimit: res.headers,
|
|
149
149
|
});
|
|
@@ -155,7 +155,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
155
155
|
const res = directId
|
|
156
156
|
? await fetchJson(ctx, `/cs2/teams/${encodeURIComponent(idOrSlug)}`)
|
|
157
157
|
: await fetchJson(ctx, '/cs2/teams', { query: { search: idOrSlug, limit: 5 } });
|
|
158
|
-
|
|
158
|
+
apiCalls += 1;
|
|
159
159
|
rateLimit = res.headers;
|
|
160
160
|
if (!res.ok) {
|
|
161
161
|
return errorEnvelope({
|
|
@@ -165,7 +165,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
165
165
|
source: 'team_profile',
|
|
166
166
|
requestId,
|
|
167
167
|
tookMs: Date.now() - started,
|
|
168
|
-
|
|
168
|
+
apiCalls,
|
|
169
169
|
httpStatus: res.status,
|
|
170
170
|
rateLimit: res.headers,
|
|
171
171
|
recover: [
|
|
@@ -192,7 +192,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
192
192
|
source: 'team_profile',
|
|
193
193
|
requestId,
|
|
194
194
|
tookMs: Date.now() - started,
|
|
195
|
-
|
|
195
|
+
apiCalls,
|
|
196
196
|
rateLimit: res.headers,
|
|
197
197
|
recover: [
|
|
198
198
|
'Call resolve_entity with the team name and type=team',
|
|
@@ -204,7 +204,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
204
204
|
}
|
|
205
205
|
else if (game === 'dota2') {
|
|
206
206
|
const res = await fetchJson(ctx, '/dota2/teams', { query: { search: idOrSlug, limit: 5 } });
|
|
207
|
-
|
|
207
|
+
apiCalls += 1;
|
|
208
208
|
rateLimit = res.headers;
|
|
209
209
|
if (!res.ok) {
|
|
210
210
|
return errorEnvelope({
|
|
@@ -214,7 +214,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
214
214
|
source: 'team_profile',
|
|
215
215
|
requestId,
|
|
216
216
|
tookMs: Date.now() - started,
|
|
217
|
-
|
|
217
|
+
apiCalls,
|
|
218
218
|
httpStatus: res.status,
|
|
219
219
|
rateLimit: res.headers,
|
|
220
220
|
recover: [
|
|
@@ -238,7 +238,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
238
238
|
source: 'team_profile',
|
|
239
239
|
requestId,
|
|
240
240
|
tookMs: Date.now() - started,
|
|
241
|
-
|
|
241
|
+
apiCalls,
|
|
242
242
|
rateLimit: res.headers,
|
|
243
243
|
recover: [
|
|
244
244
|
'Call resolve_entity with the team name and type=team',
|
|
@@ -250,7 +250,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
250
250
|
}
|
|
251
251
|
else if (game === 'cod') {
|
|
252
252
|
const res = await fetchJson(ctx, `/cod/orgs/${encodeURIComponent(slug || idOrSlug)}`);
|
|
253
|
-
|
|
253
|
+
apiCalls += 1;
|
|
254
254
|
rateLimit = res.headers;
|
|
255
255
|
if (!res.ok) {
|
|
256
256
|
return errorEnvelope({
|
|
@@ -260,7 +260,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
260
260
|
source: 'team_profile',
|
|
261
261
|
requestId,
|
|
262
262
|
tookMs: Date.now() - started,
|
|
263
|
-
|
|
263
|
+
apiCalls,
|
|
264
264
|
httpStatus: res.status,
|
|
265
265
|
rateLimit: res.headers,
|
|
266
266
|
});
|
|
@@ -279,7 +279,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
279
279
|
if (game === 'lol') {
|
|
280
280
|
tasks.push((async () => {
|
|
281
281
|
const res = await fetchJson(ctx, `/lol/teams/${encodeURIComponent(team.slug || idOrSlug)}/roster`);
|
|
282
|
-
|
|
282
|
+
apiCalls += 1;
|
|
283
283
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
284
284
|
if (res.ok) {
|
|
285
285
|
roster = {
|
|
@@ -301,7 +301,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
301
301
|
const res = await fetchJson(ctx, `/lol/teams/${encodeURIComponent(slug)}/matches`, {
|
|
302
302
|
query: { limit: String(recentLimit) },
|
|
303
303
|
});
|
|
304
|
-
|
|
304
|
+
apiCalls += 1;
|
|
305
305
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
306
306
|
if (res.ok) {
|
|
307
307
|
const startMs = (row) => {
|
|
@@ -340,7 +340,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
340
340
|
const tid = team.id !== 'unknown' ? team.id : idOrSlug;
|
|
341
341
|
tasks.push((async () => {
|
|
342
342
|
const res = await fetchJson(ctx, `/cs2/teams/${encodeURIComponent(tid)}/roster`);
|
|
343
|
-
|
|
343
|
+
apiCalls += 1;
|
|
344
344
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
345
345
|
if (res.ok) {
|
|
346
346
|
roster = {
|
|
@@ -359,7 +359,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
359
359
|
})());
|
|
360
360
|
tasks.push((async () => {
|
|
361
361
|
const res = await fetchJson(ctx, `/cs2/teams/${encodeURIComponent(tid)}/trends`);
|
|
362
|
-
|
|
362
|
+
apiCalls += 1;
|
|
363
363
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
364
364
|
if (res.ok)
|
|
365
365
|
form = unwrapPayload(res.data) ?? res.data;
|
|
@@ -375,7 +375,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
375
375
|
const res = await fetchJson(ctx, '/cs2/matches', {
|
|
376
376
|
query: { team: tid, limit: recentLimit, status: 'completed' },
|
|
377
377
|
});
|
|
378
|
-
|
|
378
|
+
apiCalls += 1;
|
|
379
379
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
380
380
|
if (res.ok) {
|
|
381
381
|
recentMatches = extractRows(res.data)
|
|
@@ -400,7 +400,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
400
400
|
}));
|
|
401
401
|
tasks.push((async () => {
|
|
402
402
|
const res = await fetchJson(ctx, `/dota2/teams/${encodeURIComponent(idOrSlug)}/radar`);
|
|
403
|
-
|
|
403
|
+
apiCalls += 1;
|
|
404
404
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
405
405
|
if (res.ok)
|
|
406
406
|
form = { radar: res.data };
|
|
@@ -414,7 +414,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
414
414
|
})());
|
|
415
415
|
tasks.push((async () => {
|
|
416
416
|
const res = await fetchJson(ctx, '/dota2/matches/recent', { query: { limit: recentLimit } });
|
|
417
|
-
|
|
417
|
+
apiCalls += 1;
|
|
418
418
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
419
419
|
if (res.ok) {
|
|
420
420
|
const t = idOrSlug.toLowerCase();
|
|
@@ -443,7 +443,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
443
443
|
const res = await fetchJson(ctx, `/cod/orgs/${encodeURIComponent(team.slug || idOrSlug)}/roster`, {
|
|
444
444
|
query: { includeFormer: args.includeFormer === true },
|
|
445
445
|
});
|
|
446
|
-
|
|
446
|
+
apiCalls += 1;
|
|
447
447
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
448
448
|
if (res.ok) {
|
|
449
449
|
roster = {
|
|
@@ -464,7 +464,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
464
464
|
const res = await fetchJson(ctx, '/cod/matches', {
|
|
465
465
|
query: { team: team.slug || idOrSlug, limit: recentLimit },
|
|
466
466
|
});
|
|
467
|
-
|
|
467
|
+
apiCalls += 1;
|
|
468
468
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
469
469
|
if (res.ok) {
|
|
470
470
|
recentMatches = extractRows(res.data)
|
|
@@ -486,7 +486,7 @@ Example: { "game": "lol", "slug": "t1", "recentLimit": 10 }`,
|
|
|
486
486
|
game,
|
|
487
487
|
requestId,
|
|
488
488
|
tookMs: Date.now() - started,
|
|
489
|
-
|
|
489
|
+
apiCalls,
|
|
490
490
|
rateLimit,
|
|
491
491
|
partial: partial.length ? partial : undefined,
|
|
492
492
|
entities: {
|
|
@@ -532,21 +532,21 @@ function winnerSide(match, a) {
|
|
|
532
532
|
}
|
|
533
533
|
export const headToHead = {
|
|
534
534
|
name: 'head_to_head',
|
|
535
|
-
description: `Head-to-head record between two teams, two UFC fighters, or two tennis players. Tennis reads the tennis head-to-head route; other games filter match history server-side.
|
|
536
|
-
|
|
537
|
-
When to use:
|
|
538
|
-
- Rivalry / series record questions
|
|
539
|
-
- Supporting context for previews
|
|
540
|
-
|
|
541
|
-
Prefer over: agent-side double match-list filtering.
|
|
542
|
-
|
|
543
|
-
Do not use when: single-side form only → team_profile or player_profile.
|
|
544
|
-
|
|
545
|
-
Caveat: Dota filters are weaker; expect meta.warnings when data is sparse.
|
|
546
|
-
|
|
547
|
-
Tennis: pass entityType "player" with sideA and sideB (ids like atp_207989, or names). This tool has no players param; for three or more players use h2h_matrix, which does. limit pages the meetings list only (max 50); every tally covers all meetings. In the payload current_rank is null when a player is not on the tour's latest list: read last_rank. The tiebreak and level_breakdown keys are in data.h2h.
|
|
548
|
-
|
|
549
|
-
Parallel-safe: yes. API calls: 2–4.
|
|
535
|
+
description: `Head-to-head record between two teams, two UFC fighters, or two tennis players. Tennis reads the tennis head-to-head route; other games filter match history server-side.
|
|
536
|
+
|
|
537
|
+
When to use:
|
|
538
|
+
- Rivalry / series record questions
|
|
539
|
+
- Supporting context for previews
|
|
540
|
+
|
|
541
|
+
Prefer over: agent-side double match-list filtering.
|
|
542
|
+
|
|
543
|
+
Do not use when: single-side form only → team_profile or player_profile.
|
|
544
|
+
|
|
545
|
+
Caveat: Dota filters are weaker; expect meta.warnings when data is sparse.
|
|
546
|
+
|
|
547
|
+
Tennis: pass entityType "player" with sideA and sideB (ids like atp_207989, or names). This tool has no players param; for three or more players use h2h_matrix, which does. limit pages the meetings list only (max 50); every tally covers all meetings. In the payload current_rank is null when a player is not on the tour's latest list: read last_rank. The tiebreak and level_breakdown keys are in data.h2h.
|
|
548
|
+
|
|
549
|
+
Parallel-safe: yes. API calls: 2–4.
|
|
550
550
|
Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
551
551
|
inputSchema: {
|
|
552
552
|
type: 'object',
|
|
@@ -600,7 +600,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
600
600
|
const fromMs = fromIso ? Date.parse(fromIso) : NaN;
|
|
601
601
|
const toMs = toIso ? Date.parse(toIso) : NaN;
|
|
602
602
|
const warnings = [];
|
|
603
|
-
let
|
|
603
|
+
let apiCalls = 0;
|
|
604
604
|
let rateLimit = {};
|
|
605
605
|
let rows = [];
|
|
606
606
|
let matchA = sideA;
|
|
@@ -608,7 +608,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
608
608
|
if (game === 'cs2') {
|
|
609
609
|
const resolveCs2Side = async (side) => {
|
|
610
610
|
const res = await fetchJson(ctx, '/cs2/teams', { query: { search: side, limit: 5 } });
|
|
611
|
-
|
|
611
|
+
apiCalls += 1;
|
|
612
612
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
613
613
|
if (!res.ok)
|
|
614
614
|
return null;
|
|
@@ -627,7 +627,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
627
627
|
const res = await fetchJson(ctx, `/cs2/teams/${encodeURIComponent(cs2SideA.id)}/vs/${encodeURIComponent(cs2SideB.id)}`, {
|
|
628
628
|
query: { limit: 50 },
|
|
629
629
|
});
|
|
630
|
-
|
|
630
|
+
apiCalls += 1;
|
|
631
631
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
632
632
|
if (res.ok) {
|
|
633
633
|
rows = extractRows(res.data).map((row) => {
|
|
@@ -654,7 +654,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
654
654
|
}
|
|
655
655
|
if (!restH2hOk) {
|
|
656
656
|
const res = await fetchJson(ctx, '/cs2/matches', { query: { team: sideA, limit: 50 } });
|
|
657
|
-
|
|
657
|
+
apiCalls += 1;
|
|
658
658
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
659
659
|
if (!res.ok) {
|
|
660
660
|
return errorEnvelope({
|
|
@@ -664,7 +664,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
664
664
|
source: 'head_to_head',
|
|
665
665
|
requestId,
|
|
666
666
|
tookMs: Date.now() - started,
|
|
667
|
-
|
|
667
|
+
apiCalls,
|
|
668
668
|
httpStatus: res.status,
|
|
669
669
|
rateLimit: res.headers,
|
|
670
670
|
});
|
|
@@ -674,7 +674,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
674
674
|
}
|
|
675
675
|
else if (game === 'cod') {
|
|
676
676
|
const res = await fetchJson(ctx, '/cod/matches', { query: { team: sideA, limit: 50 } });
|
|
677
|
-
|
|
677
|
+
apiCalls += 1;
|
|
678
678
|
rateLimit = res.headers;
|
|
679
679
|
if (!res.ok) {
|
|
680
680
|
return errorEnvelope({
|
|
@@ -684,7 +684,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
684
684
|
source: 'head_to_head',
|
|
685
685
|
requestId,
|
|
686
686
|
tookMs: Date.now() - started,
|
|
687
|
-
|
|
687
|
+
apiCalls,
|
|
688
688
|
httpStatus: res.status,
|
|
689
689
|
rateLimit: res.headers,
|
|
690
690
|
});
|
|
@@ -693,7 +693,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
693
693
|
}
|
|
694
694
|
else if (game === 'lol') {
|
|
695
695
|
const h2hRes = await fetchJson(ctx, `/lol/teams/${encodeURIComponent(sideA)}/h2h/${encodeURIComponent(sideB)}`, { query: { limit: String(limit) } });
|
|
696
|
-
|
|
696
|
+
apiCalls += 1;
|
|
697
697
|
rateLimit = { ...rateLimit, ...h2hRes.headers };
|
|
698
698
|
if (h2hRes.ok) {
|
|
699
699
|
const env = asRecord(h2hRes.data) ?? {};
|
|
@@ -727,7 +727,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
727
727
|
game,
|
|
728
728
|
requestId,
|
|
729
729
|
tookMs: Date.now() - started,
|
|
730
|
-
|
|
730
|
+
apiCalls,
|
|
731
731
|
rateLimit,
|
|
732
732
|
data: {
|
|
733
733
|
sideA: { idOrSlug: sideA },
|
|
@@ -749,7 +749,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
749
749
|
const res = await fetchJson(ctx, '/lol/schedule', {
|
|
750
750
|
query: { teamSlug: sideA, completed: 'true', limit: '50' },
|
|
751
751
|
});
|
|
752
|
-
|
|
752
|
+
apiCalls += 1;
|
|
753
753
|
rateLimit = res.headers;
|
|
754
754
|
if (!res.ok) {
|
|
755
755
|
return errorEnvelope({
|
|
@@ -759,7 +759,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
759
759
|
source: 'head_to_head',
|
|
760
760
|
requestId,
|
|
761
761
|
tookMs: Date.now() - started,
|
|
762
|
-
|
|
762
|
+
apiCalls,
|
|
763
763
|
httpStatus: res.status,
|
|
764
764
|
rateLimit: res.headers,
|
|
765
765
|
});
|
|
@@ -769,7 +769,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
769
769
|
else if (game === 'dota2') {
|
|
770
770
|
warnings.push('Dota H2H uses recent matches with weak filters; record may be incomplete');
|
|
771
771
|
const res = await fetchJson(ctx, '/dota2/matches/recent', { query: { limit: 50 } });
|
|
772
|
-
|
|
772
|
+
apiCalls += 1;
|
|
773
773
|
rateLimit = res.headers;
|
|
774
774
|
if (!res.ok) {
|
|
775
775
|
return errorEnvelope({
|
|
@@ -779,7 +779,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
779
779
|
source: 'head_to_head',
|
|
780
780
|
requestId,
|
|
781
781
|
tookMs: Date.now() - started,
|
|
782
|
-
|
|
782
|
+
apiCalls,
|
|
783
783
|
httpStatus: res.status,
|
|
784
784
|
rateLimit: res.headers,
|
|
785
785
|
});
|
|
@@ -793,7 +793,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
793
793
|
fetchJson(ctx, `/ufc/fighters/${encodeURIComponent(sideA)}/fights`, { query: { limit: 50 } }),
|
|
794
794
|
fetchJson(ctx, `/ufc/fighters/${encodeURIComponent(sideB)}/fights`, { query: { limit: 50 } }),
|
|
795
795
|
]);
|
|
796
|
-
|
|
796
|
+
apiCalls += 4;
|
|
797
797
|
rateLimit = { ...aRes.headers, ...bRes.headers, ...histA.headers, ...histB.headers };
|
|
798
798
|
if (!aRes.ok || !bRes.ok) {
|
|
799
799
|
return errorEnvelope({
|
|
@@ -803,7 +803,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
803
803
|
source: 'head_to_head',
|
|
804
804
|
requestId,
|
|
805
805
|
tookMs: Date.now() - started,
|
|
806
|
-
|
|
806
|
+
apiCalls,
|
|
807
807
|
recover: [
|
|
808
808
|
'resolve_entity { game: "ufc", type: "fighter", q } for each name',
|
|
809
809
|
'Retry head_to_head with returned slugs as sideA/sideB',
|
|
@@ -833,7 +833,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
833
833
|
if (/^(atp|wta)_\d+$/i.test(side))
|
|
834
834
|
return { id: side, name: side };
|
|
835
835
|
const res = await fetchJson(ctx, '/tennis/players/search', { query: { q: side, limit: 3 } });
|
|
836
|
-
|
|
836
|
+
apiCalls += 1;
|
|
837
837
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
838
838
|
if (!res.ok)
|
|
839
839
|
return null;
|
|
@@ -852,7 +852,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
852
852
|
source: 'head_to_head',
|
|
853
853
|
requestId,
|
|
854
854
|
tookMs: Date.now() - started,
|
|
855
|
-
|
|
855
|
+
apiCalls,
|
|
856
856
|
rateLimit,
|
|
857
857
|
recover: [
|
|
858
858
|
'resolve_entity { game: "tennis", type: "player", q } for each name',
|
|
@@ -864,7 +864,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
864
864
|
if (args.limit !== undefined && args.limit !== null)
|
|
865
865
|
tennisQuery.limit = limit;
|
|
866
866
|
const res = await fetchJson(ctx, '/tennis/h2h', { query: tennisQuery });
|
|
867
|
-
|
|
867
|
+
apiCalls += 1;
|
|
868
868
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
869
869
|
if (!res.ok) {
|
|
870
870
|
return errorEnvelope({
|
|
@@ -874,7 +874,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
874
874
|
source: 'head_to_head',
|
|
875
875
|
requestId,
|
|
876
876
|
tookMs: Date.now() - started,
|
|
877
|
-
|
|
877
|
+
apiCalls,
|
|
878
878
|
httpStatus: res.status,
|
|
879
879
|
rateLimit,
|
|
880
880
|
});
|
|
@@ -890,7 +890,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
890
890
|
game,
|
|
891
891
|
requestId,
|
|
892
892
|
tookMs: Date.now() - started,
|
|
893
|
-
|
|
893
|
+
apiCalls,
|
|
894
894
|
rateLimit,
|
|
895
895
|
data: {
|
|
896
896
|
sideA: { idOrSlug: tA.id, name: sideAName },
|
|
@@ -952,7 +952,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
952
952
|
game,
|
|
953
953
|
requestId,
|
|
954
954
|
tookMs: Date.now() - started,
|
|
955
|
-
|
|
955
|
+
apiCalls,
|
|
956
956
|
rateLimit,
|
|
957
957
|
warnings: warnings.length ? warnings : undefined,
|
|
958
958
|
data: {
|
|
@@ -972,18 +972,18 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
972
972
|
};
|
|
973
973
|
export const h2hMatrix = {
|
|
974
974
|
name: 'h2h_matrix',
|
|
975
|
-
description: `Multi-player tennis head-to-head grid: every pair's series record in one comparison matrix.
|
|
976
|
-
|
|
977
|
-
When to use:
|
|
978
|
-
- Draw/field analysis: how each contender fares against every other (e.g. Alcaraz vs Zverev vs Sinner round-robin records)
|
|
979
|
-
- Group-stage or semifinal-field comparisons
|
|
980
|
-
|
|
981
|
-
Prefer over: N head_to_head calls for an N-player field; raw matrix via call_api.
|
|
982
|
-
|
|
983
|
-
Do not use when: a two-player rivalry deep-dive with tiebreak/decider splits → head_to_head; season stat leaders → leaderboard_*; rankings → standings.
|
|
984
|
-
|
|
985
|
-
Tennis-only. players takes 2-16 ids or names (names resolve via player search). Each matrix cell is "W-L" from the row player's perspective, "-" on the diagonal.
|
|
986
|
-
|
|
975
|
+
description: `Multi-player tennis head-to-head grid: every pair's series record in one comparison matrix.
|
|
976
|
+
|
|
977
|
+
When to use:
|
|
978
|
+
- Draw/field analysis: how each contender fares against every other (e.g. Alcaraz vs Zverev vs Sinner round-robin records)
|
|
979
|
+
- Group-stage or semifinal-field comparisons
|
|
980
|
+
|
|
981
|
+
Prefer over: N head_to_head calls for an N-player field; raw matrix via call_api.
|
|
982
|
+
|
|
983
|
+
Do not use when: a two-player rivalry deep-dive with tiebreak/decider splits → head_to_head; season stat leaders → leaderboard_*; rankings → standings.
|
|
984
|
+
|
|
985
|
+
Tennis-only. players takes 2-16 ids or names (names resolve via player search). Each matrix cell is "W-L" from the row player's perspective, "-" on the diagonal.
|
|
986
|
+
|
|
987
987
|
Parallel-safe: yes. API calls: 1 + one search per unresolved name.`,
|
|
988
988
|
inputSchema: {
|
|
989
989
|
type: 'object',
|
|
@@ -1044,13 +1044,13 @@ Parallel-safe: yes. API calls: 1 + one search per unresolved name.`,
|
|
|
1044
1044
|
recover: ['Pass 2-16 player ids (atp_207989) or names ("Carlos Alcaraz")'],
|
|
1045
1045
|
});
|
|
1046
1046
|
}
|
|
1047
|
-
let
|
|
1047
|
+
let apiCalls = 0;
|
|
1048
1048
|
let rateLimit = {};
|
|
1049
1049
|
const resolveTennisSide = async (side) => {
|
|
1050
1050
|
if (/^(atp|wta)_\d+$/i.test(side))
|
|
1051
1051
|
return { id: side, name: side };
|
|
1052
1052
|
const res = await fetchJson(ctx, '/tennis/players/search', { query: { q: side, limit: 3 } });
|
|
1053
|
-
|
|
1053
|
+
apiCalls += 1;
|
|
1054
1054
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
1055
1055
|
if (!res.ok)
|
|
1056
1056
|
return null;
|
|
@@ -1070,7 +1070,7 @@ Parallel-safe: yes. API calls: 1 + one search per unresolved name.`,
|
|
|
1070
1070
|
source: 'h2h_matrix',
|
|
1071
1071
|
requestId,
|
|
1072
1072
|
tookMs: Date.now() - started,
|
|
1073
|
-
|
|
1073
|
+
apiCalls,
|
|
1074
1074
|
rateLimit,
|
|
1075
1075
|
recover: [
|
|
1076
1076
|
'resolve_entity { game: "tennis", type: "player", q } for each missing name',
|
|
@@ -1087,14 +1087,14 @@ Parallel-safe: yes. API calls: 1 + one search per unresolved name.`,
|
|
|
1087
1087
|
source: 'h2h_matrix',
|
|
1088
1088
|
requestId,
|
|
1089
1089
|
tookMs: Date.now() - started,
|
|
1090
|
-
|
|
1090
|
+
apiCalls,
|
|
1091
1091
|
rateLimit,
|
|
1092
1092
|
});
|
|
1093
1093
|
}
|
|
1094
1094
|
const res = await fetchJson(ctx, '/tennis/h2h/matrix', {
|
|
1095
1095
|
query: { player_ids: ids.join(',') },
|
|
1096
1096
|
});
|
|
1097
|
-
|
|
1097
|
+
apiCalls += 1;
|
|
1098
1098
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
1099
1099
|
if (!res.ok) {
|
|
1100
1100
|
return errorEnvelope({
|
|
@@ -1104,7 +1104,7 @@ Parallel-safe: yes. API calls: 1 + one search per unresolved name.`,
|
|
|
1104
1104
|
source: 'h2h_matrix',
|
|
1105
1105
|
requestId,
|
|
1106
1106
|
tookMs: Date.now() - started,
|
|
1107
|
-
|
|
1107
|
+
apiCalls,
|
|
1108
1108
|
httpStatus: res.status,
|
|
1109
1109
|
rateLimit,
|
|
1110
1110
|
});
|
|
@@ -1125,7 +1125,7 @@ Parallel-safe: yes. API calls: 1 + one search per unresolved name.`,
|
|
|
1125
1125
|
game,
|
|
1126
1126
|
requestId,
|
|
1127
1127
|
tookMs: Date.now() - started,
|
|
1128
|
-
|
|
1128
|
+
apiCalls,
|
|
1129
1129
|
rateLimit,
|
|
1130
1130
|
data: {
|
|
1131
1131
|
players,
|
|
@@ -202,7 +202,7 @@ Parallel-safe: yes. API calls: 1.`,
|
|
|
202
202
|
source: 'tournaments',
|
|
203
203
|
requestId,
|
|
204
204
|
tookMs: Date.now() - started,
|
|
205
|
-
|
|
205
|
+
apiCalls: 1,
|
|
206
206
|
httpStatus: res.status,
|
|
207
207
|
rateLimit: res.headers,
|
|
208
208
|
});
|
|
@@ -248,7 +248,7 @@ Parallel-safe: yes. API calls: 1.`,
|
|
|
248
248
|
game,
|
|
249
249
|
requestId,
|
|
250
250
|
tookMs: Date.now() - started,
|
|
251
|
-
|
|
251
|
+
apiCalls: 1,
|
|
252
252
|
rateLimit: res.headers,
|
|
253
253
|
warnings: warnings.length ? warnings : undefined,
|
|
254
254
|
data: {
|
package/dist/tools/webhooks.js
CHANGED
|
@@ -25,7 +25,7 @@ async function callApi(ctx, tool, method, path, opts = {}, shape = (b) => b) {
|
|
|
25
25
|
...(body.code || body.upgradeUrl ? { details: { ...(body.code ? { apiCode: body.code } : {}), ...(body.upgradeUrl ? { upgradeUrl: body.upgradeUrl, currentPlan: body.currentPlan, requiredPlan: body.requiredPlan } : {}) } } : {}),
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
-
return successEnvelope({ data: shape(body), game: null, source: tool, tookMs: Date.now() - started,
|
|
28
|
+
return successEnvelope({ data: shape(body), game: null, source: tool, tookMs: Date.now() - started, apiCalls: 1 });
|
|
29
29
|
}
|
|
30
30
|
function bad(tool, message, recover) {
|
|
31
31
|
return errorEnvelope({ code: 'VALIDATION', message, game: null, source: tool, retryable: false, recover });
|
|
@@ -119,7 +119,7 @@ export const webhookTools = [
|
|
|
119
119
|
},
|
|
120
120
|
game: null,
|
|
121
121
|
source: 'explain_webhook_setup',
|
|
122
|
-
|
|
122
|
+
apiCalls: 3,
|
|
123
123
|
});
|
|
124
124
|
},
|
|
125
125
|
},
|
package/package.json
CHANGED
|
@@ -1,52 +1,51 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "cito-mcp",
|
|
3
|
-
"version": "0.6.
|
|
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
|
-
"type": "module",
|
|
6
|
-
"homepage": "https://cito.gg",
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"counter-strike",
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "cito-mcp",
|
|
3
|
+
"version": "0.6.1",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"homepage": "https://cito.gg",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"mcp",
|
|
9
|
+
"model-context-protocol",
|
|
10
|
+
"esports",
|
|
11
|
+
"cs2",
|
|
12
|
+
"counter-strike",
|
|
13
|
+
"counter-strike-2",
|
|
14
|
+
"lol",
|
|
15
|
+
"league-of-legends",
|
|
16
|
+
"dota2",
|
|
17
|
+
"cod",
|
|
18
|
+
"ufc",
|
|
19
|
+
"mma",
|
|
20
|
+
"bellator",
|
|
21
|
+
"tennis",
|
|
22
|
+
"live-scores",
|
|
23
|
+
"sports-data",
|
|
24
|
+
"esports-api"
|
|
25
|
+
],
|
|
26
|
+
"bin": {
|
|
27
|
+
"cito-mcp": "dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc -p tsconfig.json",
|
|
38
|
+
"test": "tsx --test src/**/*.test.ts src/*.test.ts",
|
|
39
|
+
"start": "node dist/index.js",
|
|
40
|
+
"prepublishOnly": "npm run build && npm test && npm run smoke",
|
|
41
|
+
"smoke": "node scripts/smoke.mjs"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "1.29.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^22.10.0",
|
|
48
|
+
"tsx": "^4.23.1",
|
|
49
|
+
"typescript": "^5.9.2"
|
|
50
|
+
}
|
|
51
|
+
}
|