cito-mcp 0.5.8 → 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/README.md +21 -21
- package/dist/client.js +8 -8
- package/dist/envelope.js +11 -11
- package/dist/index.js +2 -2
- package/dist/install.js +15 -15
- package/dist/instructions.js +10 -4
- package/dist/pacing.js +2 -2
- package/dist/scrub.js +87 -11
- package/dist/tools/combat.js +96 -96
- package/dist/tools/index.js +2 -0
- package/dist/tools/insight.js +48 -46
- package/dist/tools/leaderboard.js +6 -6
- package/dist/tools/live.js +45 -44
- package/dist/tools/match.js +90 -43
- package/dist/tools/meta.js +108 -73
- package/dist/tools/normalize.js +3 -2
- package/dist/tools/player.js +67 -28
- package/dist/tools/rankings.js +42 -9
- package/dist/tools/resolve.js +61 -61
- package/dist/tools/schedule.js +15 -14
- package/dist/tools/standings.js +66 -46
- package/dist/tools/team.js +91 -89
- package/dist/tools/tournaments.js +7 -9
- package/dist/tools/types.js +1 -1
- package/dist/tools/webhooks.js +377 -0
- 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.
|
|
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,19 +532,21 @@ function winnerSide(match, a) {
|
|
|
532
532
|
}
|
|
533
533
|
export const headToHead = {
|
|
534
534
|
name: 'head_to_head',
|
|
535
|
-
description: `
|
|
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
|
-
|
|
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.
|
|
548
550
|
Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
549
551
|
inputSchema: {
|
|
550
552
|
type: 'object',
|
|
@@ -558,8 +560,8 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
558
560
|
default: 'team',
|
|
559
561
|
description: 'team (default), fighter (UFC), or player (tennis).',
|
|
560
562
|
},
|
|
561
|
-
sideA: stringSchema('Id or slug for side A.', 'faze'),
|
|
562
|
-
sideB: stringSchema('Id or slug for side B.', 'navi'),
|
|
563
|
+
sideA: stringSchema('Id or slug for side A (tennis: atp_207989 or a player name).', 'faze'),
|
|
564
|
+
sideB: stringSchema('Id or slug for side B (tennis: atp_206173 or a player name).', 'navi'),
|
|
563
565
|
limit: limitSchema({ default: 20, max: 50, description: 'Max meetings returned.' }),
|
|
564
566
|
from: stringSchema('ISO-8601 lower bound on meeting startTime (client filter).'),
|
|
565
567
|
to: stringSchema('ISO-8601 upper bound on meeting startTime (client filter).'),
|
|
@@ -598,7 +600,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
598
600
|
const fromMs = fromIso ? Date.parse(fromIso) : NaN;
|
|
599
601
|
const toMs = toIso ? Date.parse(toIso) : NaN;
|
|
600
602
|
const warnings = [];
|
|
601
|
-
let
|
|
603
|
+
let apiCalls = 0;
|
|
602
604
|
let rateLimit = {};
|
|
603
605
|
let rows = [];
|
|
604
606
|
let matchA = sideA;
|
|
@@ -606,7 +608,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
606
608
|
if (game === 'cs2') {
|
|
607
609
|
const resolveCs2Side = async (side) => {
|
|
608
610
|
const res = await fetchJson(ctx, '/cs2/teams', { query: { search: side, limit: 5 } });
|
|
609
|
-
|
|
611
|
+
apiCalls += 1;
|
|
610
612
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
611
613
|
if (!res.ok)
|
|
612
614
|
return null;
|
|
@@ -625,7 +627,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
625
627
|
const res = await fetchJson(ctx, `/cs2/teams/${encodeURIComponent(cs2SideA.id)}/vs/${encodeURIComponent(cs2SideB.id)}`, {
|
|
626
628
|
query: { limit: 50 },
|
|
627
629
|
});
|
|
628
|
-
|
|
630
|
+
apiCalls += 1;
|
|
629
631
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
630
632
|
if (res.ok) {
|
|
631
633
|
rows = extractRows(res.data).map((row) => {
|
|
@@ -652,7 +654,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
652
654
|
}
|
|
653
655
|
if (!restH2hOk) {
|
|
654
656
|
const res = await fetchJson(ctx, '/cs2/matches', { query: { team: sideA, limit: 50 } });
|
|
655
|
-
|
|
657
|
+
apiCalls += 1;
|
|
656
658
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
657
659
|
if (!res.ok) {
|
|
658
660
|
return errorEnvelope({
|
|
@@ -662,7 +664,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
662
664
|
source: 'head_to_head',
|
|
663
665
|
requestId,
|
|
664
666
|
tookMs: Date.now() - started,
|
|
665
|
-
|
|
667
|
+
apiCalls,
|
|
666
668
|
httpStatus: res.status,
|
|
667
669
|
rateLimit: res.headers,
|
|
668
670
|
});
|
|
@@ -672,7 +674,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
672
674
|
}
|
|
673
675
|
else if (game === 'cod') {
|
|
674
676
|
const res = await fetchJson(ctx, '/cod/matches', { query: { team: sideA, limit: 50 } });
|
|
675
|
-
|
|
677
|
+
apiCalls += 1;
|
|
676
678
|
rateLimit = res.headers;
|
|
677
679
|
if (!res.ok) {
|
|
678
680
|
return errorEnvelope({
|
|
@@ -682,7 +684,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
682
684
|
source: 'head_to_head',
|
|
683
685
|
requestId,
|
|
684
686
|
tookMs: Date.now() - started,
|
|
685
|
-
|
|
687
|
+
apiCalls,
|
|
686
688
|
httpStatus: res.status,
|
|
687
689
|
rateLimit: res.headers,
|
|
688
690
|
});
|
|
@@ -691,7 +693,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
691
693
|
}
|
|
692
694
|
else if (game === 'lol') {
|
|
693
695
|
const h2hRes = await fetchJson(ctx, `/lol/teams/${encodeURIComponent(sideA)}/h2h/${encodeURIComponent(sideB)}`, { query: { limit: String(limit) } });
|
|
694
|
-
|
|
696
|
+
apiCalls += 1;
|
|
695
697
|
rateLimit = { ...rateLimit, ...h2hRes.headers };
|
|
696
698
|
if (h2hRes.ok) {
|
|
697
699
|
const env = asRecord(h2hRes.data) ?? {};
|
|
@@ -725,7 +727,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
725
727
|
game,
|
|
726
728
|
requestId,
|
|
727
729
|
tookMs: Date.now() - started,
|
|
728
|
-
|
|
730
|
+
apiCalls,
|
|
729
731
|
rateLimit,
|
|
730
732
|
data: {
|
|
731
733
|
sideA: { idOrSlug: sideA },
|
|
@@ -747,7 +749,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
747
749
|
const res = await fetchJson(ctx, '/lol/schedule', {
|
|
748
750
|
query: { teamSlug: sideA, completed: 'true', limit: '50' },
|
|
749
751
|
});
|
|
750
|
-
|
|
752
|
+
apiCalls += 1;
|
|
751
753
|
rateLimit = res.headers;
|
|
752
754
|
if (!res.ok) {
|
|
753
755
|
return errorEnvelope({
|
|
@@ -757,7 +759,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
757
759
|
source: 'head_to_head',
|
|
758
760
|
requestId,
|
|
759
761
|
tookMs: Date.now() - started,
|
|
760
|
-
|
|
762
|
+
apiCalls,
|
|
761
763
|
httpStatus: res.status,
|
|
762
764
|
rateLimit: res.headers,
|
|
763
765
|
});
|
|
@@ -767,7 +769,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
767
769
|
else if (game === 'dota2') {
|
|
768
770
|
warnings.push('Dota H2H uses recent matches with weak filters; record may be incomplete');
|
|
769
771
|
const res = await fetchJson(ctx, '/dota2/matches/recent', { query: { limit: 50 } });
|
|
770
|
-
|
|
772
|
+
apiCalls += 1;
|
|
771
773
|
rateLimit = res.headers;
|
|
772
774
|
if (!res.ok) {
|
|
773
775
|
return errorEnvelope({
|
|
@@ -777,7 +779,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
777
779
|
source: 'head_to_head',
|
|
778
780
|
requestId,
|
|
779
781
|
tookMs: Date.now() - started,
|
|
780
|
-
|
|
782
|
+
apiCalls,
|
|
781
783
|
httpStatus: res.status,
|
|
782
784
|
rateLimit: res.headers,
|
|
783
785
|
});
|
|
@@ -791,7 +793,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
791
793
|
fetchJson(ctx, `/ufc/fighters/${encodeURIComponent(sideA)}/fights`, { query: { limit: 50 } }),
|
|
792
794
|
fetchJson(ctx, `/ufc/fighters/${encodeURIComponent(sideB)}/fights`, { query: { limit: 50 } }),
|
|
793
795
|
]);
|
|
794
|
-
|
|
796
|
+
apiCalls += 4;
|
|
795
797
|
rateLimit = { ...aRes.headers, ...bRes.headers, ...histA.headers, ...histB.headers };
|
|
796
798
|
if (!aRes.ok || !bRes.ok) {
|
|
797
799
|
return errorEnvelope({
|
|
@@ -801,7 +803,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
801
803
|
source: 'head_to_head',
|
|
802
804
|
requestId,
|
|
803
805
|
tookMs: Date.now() - started,
|
|
804
|
-
|
|
806
|
+
apiCalls,
|
|
805
807
|
recover: [
|
|
806
808
|
'resolve_entity { game: "ufc", type: "fighter", q } for each name',
|
|
807
809
|
'Retry head_to_head with returned slugs as sideA/sideB',
|
|
@@ -831,7 +833,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
831
833
|
if (/^(atp|wta)_\d+$/i.test(side))
|
|
832
834
|
return { id: side, name: side };
|
|
833
835
|
const res = await fetchJson(ctx, '/tennis/players/search', { query: { q: side, limit: 3 } });
|
|
834
|
-
|
|
836
|
+
apiCalls += 1;
|
|
835
837
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
836
838
|
if (!res.ok)
|
|
837
839
|
return null;
|
|
@@ -850,7 +852,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
850
852
|
source: 'head_to_head',
|
|
851
853
|
requestId,
|
|
852
854
|
tookMs: Date.now() - started,
|
|
853
|
-
|
|
855
|
+
apiCalls,
|
|
854
856
|
rateLimit,
|
|
855
857
|
recover: [
|
|
856
858
|
'resolve_entity { game: "tennis", type: "player", q } for each name',
|
|
@@ -862,7 +864,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
862
864
|
if (args.limit !== undefined && args.limit !== null)
|
|
863
865
|
tennisQuery.limit = limit;
|
|
864
866
|
const res = await fetchJson(ctx, '/tennis/h2h', { query: tennisQuery });
|
|
865
|
-
|
|
867
|
+
apiCalls += 1;
|
|
866
868
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
867
869
|
if (!res.ok) {
|
|
868
870
|
return errorEnvelope({
|
|
@@ -872,7 +874,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
872
874
|
source: 'head_to_head',
|
|
873
875
|
requestId,
|
|
874
876
|
tookMs: Date.now() - started,
|
|
875
|
-
|
|
877
|
+
apiCalls,
|
|
876
878
|
httpStatus: res.status,
|
|
877
879
|
rateLimit,
|
|
878
880
|
});
|
|
@@ -888,7 +890,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
888
890
|
game,
|
|
889
891
|
requestId,
|
|
890
892
|
tookMs: Date.now() - started,
|
|
891
|
-
|
|
893
|
+
apiCalls,
|
|
892
894
|
rateLimit,
|
|
893
895
|
data: {
|
|
894
896
|
sideA: { idOrSlug: tA.id, name: sideAName },
|
|
@@ -950,7 +952,7 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
950
952
|
game,
|
|
951
953
|
requestId,
|
|
952
954
|
tookMs: Date.now() - started,
|
|
953
|
-
|
|
955
|
+
apiCalls,
|
|
954
956
|
rateLimit,
|
|
955
957
|
warnings: warnings.length ? warnings : undefined,
|
|
956
958
|
data: {
|
|
@@ -970,19 +972,19 @@ Example: { "game": "cs2", "sideA": "faze", "sideB": "navi", "limit": 20 }`,
|
|
|
970
972
|
};
|
|
971
973
|
export const h2hMatrix = {
|
|
972
974
|
name: 'h2h_matrix',
|
|
973
|
-
description: `Multi-player tennis head-to-head grid: every pair's series record in one comparison matrix.
|
|
974
|
-
|
|
975
|
-
When to use:
|
|
976
|
-
- Draw/field analysis: how each contender fares against every other (e.g. Alcaraz vs Zverev vs Sinner round-robin records)
|
|
977
|
-
- Group-stage or semifinal-field comparisons
|
|
978
|
-
|
|
979
|
-
Prefer over: N head_to_head calls for an N-player field; raw matrix via call_api.
|
|
980
|
-
|
|
981
|
-
Do not use when: a two-player rivalry deep-dive with tiebreak/decider splits → head_to_head; season stat leaders → leaderboard_*; rankings → standings.
|
|
982
|
-
|
|
983
|
-
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.
|
|
984
|
-
|
|
985
|
-
Parallel-safe: yes.
|
|
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
|
+
Parallel-safe: yes. API calls: 1 + one search per unresolved name.`,
|
|
986
988
|
inputSchema: {
|
|
987
989
|
type: 'object',
|
|
988
990
|
additionalProperties: false,
|
|
@@ -1042,13 +1044,13 @@ Parallel-safe: yes. Upstream cost: 1 + one search per unresolved name.`,
|
|
|
1042
1044
|
recover: ['Pass 2-16 player ids (atp_207989) or names ("Carlos Alcaraz")'],
|
|
1043
1045
|
});
|
|
1044
1046
|
}
|
|
1045
|
-
let
|
|
1047
|
+
let apiCalls = 0;
|
|
1046
1048
|
let rateLimit = {};
|
|
1047
1049
|
const resolveTennisSide = async (side) => {
|
|
1048
1050
|
if (/^(atp|wta)_\d+$/i.test(side))
|
|
1049
1051
|
return { id: side, name: side };
|
|
1050
1052
|
const res = await fetchJson(ctx, '/tennis/players/search', { query: { q: side, limit: 3 } });
|
|
1051
|
-
|
|
1053
|
+
apiCalls += 1;
|
|
1052
1054
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
1053
1055
|
if (!res.ok)
|
|
1054
1056
|
return null;
|
|
@@ -1068,7 +1070,7 @@ Parallel-safe: yes. Upstream cost: 1 + one search per unresolved name.`,
|
|
|
1068
1070
|
source: 'h2h_matrix',
|
|
1069
1071
|
requestId,
|
|
1070
1072
|
tookMs: Date.now() - started,
|
|
1071
|
-
|
|
1073
|
+
apiCalls,
|
|
1072
1074
|
rateLimit,
|
|
1073
1075
|
recover: [
|
|
1074
1076
|
'resolve_entity { game: "tennis", type: "player", q } for each missing name',
|
|
@@ -1085,14 +1087,14 @@ Parallel-safe: yes. Upstream cost: 1 + one search per unresolved name.`,
|
|
|
1085
1087
|
source: 'h2h_matrix',
|
|
1086
1088
|
requestId,
|
|
1087
1089
|
tookMs: Date.now() - started,
|
|
1088
|
-
|
|
1090
|
+
apiCalls,
|
|
1089
1091
|
rateLimit,
|
|
1090
1092
|
});
|
|
1091
1093
|
}
|
|
1092
1094
|
const res = await fetchJson(ctx, '/tennis/h2h/matrix', {
|
|
1093
1095
|
query: { player_ids: ids.join(',') },
|
|
1094
1096
|
});
|
|
1095
|
-
|
|
1097
|
+
apiCalls += 1;
|
|
1096
1098
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
1097
1099
|
if (!res.ok) {
|
|
1098
1100
|
return errorEnvelope({
|
|
@@ -1102,7 +1104,7 @@ Parallel-safe: yes. Upstream cost: 1 + one search per unresolved name.`,
|
|
|
1102
1104
|
source: 'h2h_matrix',
|
|
1103
1105
|
requestId,
|
|
1104
1106
|
tookMs: Date.now() - started,
|
|
1105
|
-
|
|
1107
|
+
apiCalls,
|
|
1106
1108
|
httpStatus: res.status,
|
|
1107
1109
|
rateLimit,
|
|
1108
1110
|
});
|
|
@@ -1123,7 +1125,7 @@ Parallel-safe: yes. Upstream cost: 1 + one search per unresolved name.`,
|
|
|
1123
1125
|
game,
|
|
1124
1126
|
requestId,
|
|
1125
1127
|
tookMs: Date.now() - started,
|
|
1126
|
-
|
|
1128
|
+
apiCalls,
|
|
1127
1129
|
rateLimit,
|
|
1128
1130
|
data: {
|
|
1129
1131
|
players,
|
|
@@ -54,10 +54,10 @@ Tennis-only. Levels accept names (Grand Slam, WTA 1000, ATP 500, Challenger,
|
|
|
54
54
|
ITF World Tennis Tour, Davis Cup, ...) or raw tier codes (G, M, A, C, D).
|
|
55
55
|
A level name that starts with ATP or WTA also fixes the tour (and conflicts with
|
|
56
56
|
a contradicting tour= argument are rejected), so "WTA 1000" cannot return ATP
|
|
57
|
-
Masters events
|
|
58
|
-
|
|
57
|
+
Masters events.
|
|
58
|
+
The category field reports ATP 500 and ATP 250 both as atp_tour (read tier to tell them apart), and WTA 1000 events as wta_1000.
|
|
59
59
|
|
|
60
|
-
Parallel-safe: yes.
|
|
60
|
+
Parallel-safe: yes. API calls: 1.`,
|
|
61
61
|
inputSchema: {
|
|
62
62
|
type: 'object',
|
|
63
63
|
additionalProperties: false,
|
|
@@ -202,7 +202,7 @@ Parallel-safe: yes. Upstream cost: 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
|
});
|
|
@@ -232,10 +232,8 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
232
232
|
.slice(0, 5)
|
|
233
233
|
.map((t) => `${t.id} (${t.tour})`)
|
|
234
234
|
.join(', ');
|
|
235
|
-
warnings.push(`The
|
|
236
|
-
`${sample}. They were dropped
|
|
237
|
-
`PM and M), which is a server-side defect; this tool sends tour=${levelTourPrefix} as well, so a repeat of this ` +
|
|
238
|
-
`warning means the upstream filter is widening again.`);
|
|
235
|
+
warnings.push(`The level filter returned ${contradictions.length} tournament(s) that contradict level="${levelRaw}": ` +
|
|
236
|
+
`${sample}. They were dropped; this tool also sends tour=${levelTourPrefix}.`);
|
|
239
237
|
}
|
|
240
238
|
return successEnvelope({
|
|
241
239
|
pagination: {
|
|
@@ -250,7 +248,7 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
250
248
|
game,
|
|
251
249
|
requestId,
|
|
252
250
|
tookMs: Date.now() - started,
|
|
253
|
-
|
|
251
|
+
apiCalls: 1,
|
|
254
252
|
rateLimit: res.headers,
|
|
255
253
|
warnings: warnings.length ? warnings : undefined,
|
|
256
254
|
data: {
|
package/dist/tools/types.js
CHANGED
|
@@ -156,7 +156,7 @@ export async function runTool(def, args, ctx) {
|
|
|
156
156
|
catch (error) {
|
|
157
157
|
const { errorEnvelope, toMcpResult: toResult } = await import('../envelope.js');
|
|
158
158
|
return toResult(errorEnvelope({
|
|
159
|
-
code: '
|
|
159
|
+
code: 'SERVICE_ERROR',
|
|
160
160
|
message: error.message || 'Unhandled tool error',
|
|
161
161
|
game: null,
|
|
162
162
|
source: def.name,
|