cricketstudio-mcp 1.0.2 → 1.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/LICENSE +26 -216
- package/README.md +198 -153
- package/data/snapshot/graph.json +22551 -0
- package/data/snapshot/ipl-historical.json +3138 -1
- package/data/snapshot/meta.json +13 -5
- package/data/snapshot/metadata.json +5 -11
- package/data/snapshot/mlc-leaderboards.json +46214 -1217
- package/data/snapshot/mlc-league.json +164 -0
- package/data/snapshot/mlc-players.json +7386 -10
- package/data/snapshot/mlc.json +1 -1
- package/data/snapshot/players.json +700 -1497
- package/data/snapshot/research.json +78 -0
- package/data/snapshot/season-stats.json +404 -50575
- package/data/snapshot/t20wc-leaderboards.json +41927 -0
- package/data/snapshot/t20wc-league.json +258 -0
- package/data/snapshot/t20wc-players.json +146389 -0
- package/data/snapshot/t20wc-teams.json +376 -0
- package/data/snapshot/t20wc.json +18599 -0
- package/data/snapshot/team-h2h.json +26 -6
- package/data/snapshot/trends.json +2981 -88
- package/data/snapshot/venues.json +278 -39
- package/data/snapshot/wpl-leaderboards.json +37016 -0
- package/data/snapshot/wpl-league.json +194 -0
- package/data/snapshot/wpl-players.json +31465 -0
- package/data/snapshot/wpl-teams.json +94 -0
- package/data/snapshot/wpl.json +4183 -0
- package/dist/index.js +643 -13
- package/package.json +59 -55
package/dist/index.js
CHANGED
|
@@ -163,6 +163,102 @@ function getMatches(limit) {
|
|
|
163
163
|
function getSeasonStats() {
|
|
164
164
|
return loadSeasonStats();
|
|
165
165
|
}
|
|
166
|
+
var _research = null;
|
|
167
|
+
var _researchIndex = null;
|
|
168
|
+
function loadResearch() {
|
|
169
|
+
if (_research !== null) return _research;
|
|
170
|
+
_research = readJson("research.json") ?? [];
|
|
171
|
+
return _research;
|
|
172
|
+
}
|
|
173
|
+
function loadResearchIndex() {
|
|
174
|
+
if (_researchIndex !== null) return _researchIndex;
|
|
175
|
+
_researchIndex = new Map(loadResearch().map((r) => [r.id, r]));
|
|
176
|
+
return _researchIndex;
|
|
177
|
+
}
|
|
178
|
+
function getResearchReports() {
|
|
179
|
+
return loadResearch();
|
|
180
|
+
}
|
|
181
|
+
function getResearchReport(id) {
|
|
182
|
+
return loadResearchIndex().get(id) ?? null;
|
|
183
|
+
}
|
|
184
|
+
var _graphNodes = null;
|
|
185
|
+
var _graphOut = null;
|
|
186
|
+
var _graphIn = null;
|
|
187
|
+
function loadGraph() {
|
|
188
|
+
if (_graphNodes !== null) return;
|
|
189
|
+
_graphNodes = /* @__PURE__ */ new Map();
|
|
190
|
+
_graphOut = /* @__PURE__ */ new Map();
|
|
191
|
+
_graphIn = /* @__PURE__ */ new Map();
|
|
192
|
+
const doc = readJson("graph.json");
|
|
193
|
+
if (!doc) return;
|
|
194
|
+
for (const [slug, n] of Object.entries(doc.nodes ?? {})) {
|
|
195
|
+
_graphNodes.set(slug, { slug, type: n.type, name: n.name ?? null });
|
|
196
|
+
}
|
|
197
|
+
for (const e of doc.edges ?? []) {
|
|
198
|
+
if (!_graphOut.has(e.src)) _graphOut.set(e.src, []);
|
|
199
|
+
_graphOut.get(e.src).push(e);
|
|
200
|
+
if (!_graphIn.has(e.dst)) _graphIn.set(e.dst, []);
|
|
201
|
+
_graphIn.get(e.dst).push(e);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function getGraphNode(slug) {
|
|
205
|
+
loadGraph();
|
|
206
|
+
return _graphNodes.get(slug) ?? null;
|
|
207
|
+
}
|
|
208
|
+
function graphEdges(slug, opts = {}) {
|
|
209
|
+
loadGraph();
|
|
210
|
+
const { predicate, direction = "out" } = opts;
|
|
211
|
+
const collected = [];
|
|
212
|
+
if (direction === "out" || direction === "both") collected.push(..._graphOut.get(slug) ?? []);
|
|
213
|
+
if (direction === "in" || direction === "both") collected.push(..._graphIn.get(slug) ?? []);
|
|
214
|
+
return predicate ? collected.filter((e) => e.predicate === predicate) : collected;
|
|
215
|
+
}
|
|
216
|
+
function graphRelated(slug, opts = {}) {
|
|
217
|
+
loadGraph();
|
|
218
|
+
const { predicate, direction = "out", limit = 25 } = opts;
|
|
219
|
+
const seen = /* @__PURE__ */ new Set();
|
|
220
|
+
const out = [];
|
|
221
|
+
const push = (otherSlug) => {
|
|
222
|
+
if (seen.has(otherSlug)) return;
|
|
223
|
+
const n = _graphNodes.get(otherSlug);
|
|
224
|
+
if (n) {
|
|
225
|
+
seen.add(otherSlug);
|
|
226
|
+
out.push(n);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
if (direction === "out" || direction === "both") {
|
|
230
|
+
for (const e of _graphOut.get(slug) ?? []) if (!predicate || e.predicate === predicate) push(e.dst);
|
|
231
|
+
}
|
|
232
|
+
if (direction === "in" || direction === "both") {
|
|
233
|
+
for (const e of _graphIn.get(slug) ?? []) if (!predicate || e.predicate === predicate) push(e.src);
|
|
234
|
+
}
|
|
235
|
+
return out.slice(0, limit);
|
|
236
|
+
}
|
|
237
|
+
function graphPath(a, b, maxDepth = 4) {
|
|
238
|
+
loadGraph();
|
|
239
|
+
if (!_graphNodes.has(a) || !_graphNodes.has(b)) return null;
|
|
240
|
+
if (a === b) return [a];
|
|
241
|
+
const visited = /* @__PURE__ */ new Set([a]);
|
|
242
|
+
let frontier = [[a]];
|
|
243
|
+
for (let depth = 0; depth < maxDepth; depth++) {
|
|
244
|
+
const next = [];
|
|
245
|
+
for (const pathSoFar of frontier) {
|
|
246
|
+
const last = pathSoFar[pathSoFar.length - 1];
|
|
247
|
+
const nbrs = /* @__PURE__ */ new Set();
|
|
248
|
+
for (const e of _graphOut.get(last) ?? []) nbrs.add(e.dst);
|
|
249
|
+
for (const e of _graphIn.get(last) ?? []) nbrs.add(e.src);
|
|
250
|
+
for (const nb of nbrs) {
|
|
251
|
+
if (visited.has(nb)) continue;
|
|
252
|
+
const newPath = [...pathSoFar, nb];
|
|
253
|
+
if (nb === b) return newPath;
|
|
254
|
+
visited.add(nb);
|
|
255
|
+
next.push(newPath);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
frontier = next;
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
166
262
|
|
|
167
263
|
// src/tools/ipl-leaderboard.ts
|
|
168
264
|
var LEADERBOARD_ASPECTS = [
|
|
@@ -490,6 +586,108 @@ var LEADERBOARD_ASPECTS = [
|
|
|
490
586
|
ascending: true,
|
|
491
587
|
floorNote: "Minimum 240 legal deliveries bowled in overs 17\u201320.",
|
|
492
588
|
liveSeasonOnly: false
|
|
589
|
+
},
|
|
590
|
+
// ── Fours by phase ───────────────────────────────────────────────────
|
|
591
|
+
{
|
|
592
|
+
slug: "most-fours-pp",
|
|
593
|
+
title: "Most fours in the powerplay",
|
|
594
|
+
description: "Most fours hit in powerplay overs (1\u20136) across IPL career.",
|
|
595
|
+
unit: "PP fours",
|
|
596
|
+
ascending: false,
|
|
597
|
+
floorNote: "Minimum 30 balls faced in powerplay.",
|
|
598
|
+
liveSeasonOnly: false
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
slug: "most-fours-middle",
|
|
602
|
+
title: "Most fours in the middle overs",
|
|
603
|
+
description: "Most fours hit in middle overs (7\u201315) across IPL career.",
|
|
604
|
+
unit: "Middle fours",
|
|
605
|
+
ascending: false,
|
|
606
|
+
floorNote: "Minimum 30 balls faced in middle overs.",
|
|
607
|
+
liveSeasonOnly: false
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
slug: "most-fours-death",
|
|
611
|
+
title: "Most fours in the death overs",
|
|
612
|
+
description: "Most fours hit in death overs (16\u201320) across IPL career.",
|
|
613
|
+
unit: "Death fours",
|
|
614
|
+
ascending: false,
|
|
615
|
+
floorNote: "Minimum 20 balls faced in death overs.",
|
|
616
|
+
liveSeasonOnly: false
|
|
617
|
+
},
|
|
618
|
+
// ── Bowling quality metrics ──────────────────────────────────────────
|
|
619
|
+
{
|
|
620
|
+
slug: "dot-ball-pct",
|
|
621
|
+
title: "Highest dot-ball percentage",
|
|
622
|
+
description: "Highest percentage of legal deliveries resulting in no runs scored. Measures bowling pressure. Higher is better for bowlers.",
|
|
623
|
+
unit: "Dot-ball %",
|
|
624
|
+
ascending: false,
|
|
625
|
+
floorNote: "Minimum 240 legal deliveries bowled.",
|
|
626
|
+
liveSeasonOnly: false
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
slug: "boundary-dependency",
|
|
630
|
+
title: "Highest boundary dependency",
|
|
631
|
+
description: "Percentage of a batter's runs scored via boundaries (fours and sixes). High dependency can indicate vulnerability on slow surfaces.",
|
|
632
|
+
unit: "Boundary dep %",
|
|
633
|
+
ascending: false,
|
|
634
|
+
floorNote: "Minimum 300 balls faced.",
|
|
635
|
+
liveSeasonOnly: false
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
slug: "century-rate",
|
|
639
|
+
title: "Best century rate",
|
|
640
|
+
description: "Centuries per 100 innings across IPL career. Measures the frequency of three-figure scores among eligible batters.",
|
|
641
|
+
unit: "Centuries/100 inn",
|
|
642
|
+
ascending: false,
|
|
643
|
+
floorNote: "Minimum 20 innings.",
|
|
644
|
+
liveSeasonOnly: false
|
|
645
|
+
},
|
|
646
|
+
// ── Batting partnerships ─────────────────────────────────────────────
|
|
647
|
+
{
|
|
648
|
+
slug: "partnership-stands",
|
|
649
|
+
title: "Highest partnership stands",
|
|
650
|
+
description: `Largest individual batting partnership stands across the IPL historical archive (2007/08\u20132025). Ranked by runs scored together. Row slug is "batter1--batter2" \u2014 use canonical URL for the stand's match page.`,
|
|
651
|
+
unit: "Runs",
|
|
652
|
+
ascending: false,
|
|
653
|
+
floorNote: "Minimum 18 balls in the partnership.",
|
|
654
|
+
liveSeasonOnly: false
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
slug: "opening-stands",
|
|
658
|
+
title: "Highest opening partnerships",
|
|
659
|
+
description: 'Largest 1st-wicket partnership stands across the IPL historical archive. Row slug is "batter1--batter2".',
|
|
660
|
+
unit: "Runs",
|
|
661
|
+
ascending: false,
|
|
662
|
+
floorNote: "Minimum 18 balls in the partnership.",
|
|
663
|
+
liveSeasonOnly: false
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
slug: "partnership-run-rate",
|
|
667
|
+
title: "Fastest partnerships by run rate",
|
|
668
|
+
description: 'Partnerships with the highest run rate (runs per over) across the IPL historical archive. Row slug is "batter1--batter2".',
|
|
669
|
+
unit: "Run rate",
|
|
670
|
+
ascending: false,
|
|
671
|
+
floorNote: "Minimum 18 balls in the partnership.",
|
|
672
|
+
liveSeasonOnly: false
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
slug: "2nd-wicket-stands",
|
|
676
|
+
title: "Highest 2nd wicket partnerships",
|
|
677
|
+
description: 'Largest 2nd-wicket partnership stands across the IPL historical archive. Row slug is "batter1--batter2".',
|
|
678
|
+
unit: "Runs",
|
|
679
|
+
ascending: false,
|
|
680
|
+
floorNote: "Minimum 18 balls in the partnership.",
|
|
681
|
+
liveSeasonOnly: false
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
slug: "3rd-wicket-stands",
|
|
685
|
+
title: "Highest 3rd wicket partnerships",
|
|
686
|
+
description: 'Largest 3rd-wicket partnership stands across the IPL historical archive. Row slug is "batter1--batter2".',
|
|
687
|
+
unit: "Runs",
|
|
688
|
+
ascending: false,
|
|
689
|
+
floorNote: "Minimum 18 balls in the partnership.",
|
|
690
|
+
liveSeasonOnly: false
|
|
493
691
|
}
|
|
494
692
|
];
|
|
495
693
|
function getAspectMeta(slug) {
|
|
@@ -503,6 +701,8 @@ var SNAPSHOT_DIR2 = resolve2(__dirname2, "..", "data", "snapshot");
|
|
|
503
701
|
var SITE = "https://players.cricketstudio.ai";
|
|
504
702
|
var MLC_HUB = `${SITE}/leagues/mlc`;
|
|
505
703
|
var IPL_HUB = `${SITE}/leagues/ipl`;
|
|
704
|
+
var WPL_HUB = `${SITE}/leagues/wpl`;
|
|
705
|
+
var T20WC_HUB = `${SITE}/leagues/t20wc`;
|
|
506
706
|
function readSnapshotJson(name) {
|
|
507
707
|
const p = resolve2(SNAPSHOT_DIR2, name);
|
|
508
708
|
if (!existsSync2(p)) return null;
|
|
@@ -522,6 +722,12 @@ var _mlcMatches = null;
|
|
|
522
722
|
var _mlcLeague = null;
|
|
523
723
|
var _mlcLeaderboards = null;
|
|
524
724
|
var _iplHistorical = void 0;
|
|
725
|
+
var _wplLeague = null;
|
|
726
|
+
var _wplTeams = null;
|
|
727
|
+
var _wplLeaderboards = null;
|
|
728
|
+
var _t20wcLeague = null;
|
|
729
|
+
var _t20wcTeams = null;
|
|
730
|
+
var _t20wcLeaderboards = null;
|
|
525
731
|
var _rawMatches = null;
|
|
526
732
|
var _rawStandings = null;
|
|
527
733
|
var _teamIdToCode = null;
|
|
@@ -577,6 +783,30 @@ function mlcLeaderboards() {
|
|
|
577
783
|
if (!_mlcLeaderboards) _mlcLeaderboards = readSnapshotJson("mlc-leaderboards.json") ?? {};
|
|
578
784
|
return _mlcLeaderboards;
|
|
579
785
|
}
|
|
786
|
+
function wplLeague() {
|
|
787
|
+
if (!_wplLeague) _wplLeague = readSnapshotJson("wpl-league.json") ?? { seasons: [], teams: [], venues: [], playerCount: 0, totalMatches: 0, leaderboardAspects: [] };
|
|
788
|
+
return _wplLeague;
|
|
789
|
+
}
|
|
790
|
+
function wplTeams() {
|
|
791
|
+
if (!_wplTeams) _wplTeams = readSnapshotJson("wpl-teams.json") ?? [];
|
|
792
|
+
return _wplTeams;
|
|
793
|
+
}
|
|
794
|
+
function wplLeaderboards() {
|
|
795
|
+
if (!_wplLeaderboards) _wplLeaderboards = readSnapshotJson("wpl-leaderboards.json") ?? {};
|
|
796
|
+
return _wplLeaderboards;
|
|
797
|
+
}
|
|
798
|
+
function t20wcLeague() {
|
|
799
|
+
if (!_t20wcLeague) _t20wcLeague = readSnapshotJson("t20wc-league.json") ?? { seasons: [], teams: [], venues: [], playerCount: 0, totalMatches: 0, leaderboardAspects: [] };
|
|
800
|
+
return _t20wcLeague;
|
|
801
|
+
}
|
|
802
|
+
function t20wcTeams() {
|
|
803
|
+
if (!_t20wcTeams) _t20wcTeams = readSnapshotJson("t20wc-teams.json") ?? [];
|
|
804
|
+
return _t20wcTeams;
|
|
805
|
+
}
|
|
806
|
+
function t20wcLeaderboards() {
|
|
807
|
+
if (!_t20wcLeaderboards) _t20wcLeaderboards = readSnapshotJson("t20wc-leaderboards.json") ?? {};
|
|
808
|
+
return _t20wcLeaderboards;
|
|
809
|
+
}
|
|
580
810
|
function iplHistorical() {
|
|
581
811
|
if (_iplHistorical !== void 0) return _iplHistorical;
|
|
582
812
|
_iplHistorical = readSnapshotJson("ipl-historical.json");
|
|
@@ -603,6 +833,10 @@ var mlcTeamUrl = (slug) => `${MLC_HUB}/teams/${slug}`;
|
|
|
603
833
|
var mlcMatchUrl = (id) => `${MLC_HUB}/matches/${id}`;
|
|
604
834
|
var mlcMatchClaimUrl = (id, kind) => `${MLC_HUB}/matches/${id}/c/${kind}`;
|
|
605
835
|
var mlcLeaderboardUrl = (aspect) => `${MLC_HUB}/leaderboards/${aspect}`;
|
|
836
|
+
var wplTeamUrl = (slug) => `${WPL_HUB}/teams/${slug}`;
|
|
837
|
+
var wplLeaderboardUrl = (aspect) => `${WPL_HUB}/leaderboards/${aspect}`;
|
|
838
|
+
var t20wcTeamUrl = (slug) => `${T20WC_HUB}/teams/${slug}`;
|
|
839
|
+
var t20wcLeaderboardUrl = (aspect) => `${T20WC_HUB}/leaderboards/${aspect}`;
|
|
606
840
|
function dataAsOf() {
|
|
607
841
|
try {
|
|
608
842
|
return statSync(resolve2(SNAPSHOT_DIR2, "metadata.json")).mtime.toISOString();
|
|
@@ -646,7 +880,7 @@ var TOOLS = [
|
|
|
646
880
|
},
|
|
647
881
|
{
|
|
648
882
|
name: "get_season_stats",
|
|
649
|
-
description: "IPL 2026 season leaderboard from
|
|
883
|
+
description: "IPL 2026 season leaderboard from CricketStudio canonical aggregate. sortBy: runs, wickets, strike_rate, economy, ducks, single_digit_outs, catches, run_outs. Optional teamCode filter. Sample-size floors apply (\u226530 balls faced for SR, \u226515 balls bowled for economy).",
|
|
650
884
|
inputSchema: { type: "object", properties: { sortBy: { type: "string", enum: ["runs", "wickets", "strike_rate", "economy", "ducks", "single_digit_outs", "catches", "run_outs"] }, teamCode: { type: "string", description: "Optional 2\u20134 letter team code e.g. MI, RCB" }, limit: { type: "number", description: "Max rows (default 15, max 100)" } }, required: ["sortBy"], additionalProperties: false }
|
|
651
885
|
},
|
|
652
886
|
{
|
|
@@ -716,13 +950,13 @@ var TOOLS = [
|
|
|
716
950
|
},
|
|
717
951
|
{
|
|
718
952
|
name: "get_fielding_stats",
|
|
719
|
-
description: "IPL 2026 fielding: catches, run-out assists, total dismissals. Pass playerSlug for a single player, omit for the full leaderboard. Aggregated from the
|
|
953
|
+
description: "IPL 2026 fielding: catches, run-out assists, total dismissals. Pass playerSlug for a single player, omit for the full leaderboard. Aggregated from the CricketStudio canonical snapshot.",
|
|
720
954
|
inputSchema: { type: "object", properties: { playerSlug: { type: "string", description: "Omit for leaderboard" }, limit: { type: "number", description: "Leaderboard rows (default 15)" } }, additionalProperties: false }
|
|
721
955
|
},
|
|
722
956
|
// ── GROUP 2: MLC ────────────────────────────────────────────────────
|
|
723
957
|
{
|
|
724
958
|
name: "get_mlc_dataset_summary",
|
|
725
|
-
description: "First call for Major League Cricket (MLC) coverage. Returns seasons covered (2023\u20132026), corpus stats, surface URLs,
|
|
959
|
+
description: "First call for Major League Cricket (MLC) coverage. Returns seasons covered (2023\u20132026), corpus stats, surface URLs, 55 leaderboard aspects, and Cricsheet CC BY 3.0 attribution. MLC is distinct from IPL and lives under /leagues/mlc.",
|
|
726
960
|
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
727
961
|
},
|
|
728
962
|
{
|
|
@@ -757,16 +991,92 @@ var TOOLS = [
|
|
|
757
991
|
},
|
|
758
992
|
{
|
|
759
993
|
name: "list_mlc_leaderboards",
|
|
760
|
-
description: "Top-N rows of one MLC leaderboard aspect.
|
|
994
|
+
description: "Top-N rows of one MLC leaderboard aspect. 55 aspects including orange-cap, purple-cap, strike-rate, economy-leaders, most-sixes, most-fours, top-knocks, best-bowling, powerplay-strike-rate, death-overs-economy, partnership-stands, opening-stands, partnership-run-rate, 2nd-wicket-stands, 3rd-wicket-stands. Call get_mlc_dataset_summary for the full list. Sample-size floors enforced.",
|
|
761
995
|
inputSchema: { type: "object", properties: { aspect: { type: "string", description: "Leaderboard aspect slug e.g. orange-cap" }, limit: { type: "number", description: "Default 20, max 100" } }, required: ["aspect"], additionalProperties: false }
|
|
762
996
|
},
|
|
763
997
|
// ── GROUP 3: IPL Historical ─────────────────────────────────────────
|
|
764
998
|
{
|
|
765
999
|
name: "get_ipl_leaderboard",
|
|
766
|
-
description: 'IPL historical leaderboard from the 18-season Cricsheet corpus (2007/08\u20132025).
|
|
1000
|
+
description: 'IPL historical leaderboard from the 18-season Cricsheet corpus (2007/08\u20132025). 46 aspects: orange-cap, purple-cap, most-sixes, most-fours, strike-rate, economy-leaders, most-fifties, most-hundreds, batting-average, bowling-average, most-dot-balls, dot-ball-pct, boundary-dependency, century-rate, powerplay-economy, death-sr, partnership-stands, opening-stands, partnership-run-rate, 2nd-wicket-stands, 3rd-wicket-stands, and more. Pass season to scope to one year (e.g. "ipl-2024"). Returns canonical URL at /leagues/ipl/leaderboards/{aspect}.',
|
|
767
1001
|
inputSchema: { type: "object", properties: { aspect: { type: "string", description: "Leaderboard aspect e.g. orange-cap, purple-cap, most-sixes, economy-leaders" }, season: { type: "string", description: "Optional season slug e.g. ipl-2024 (omit for all-time)" }, limit: { type: "number", description: "Default 20, max 100" } }, required: ["aspect"], additionalProperties: false }
|
|
1002
|
+
},
|
|
1003
|
+
// ── GROUP 4: Research reports ────────────────────────────────────────
|
|
1004
|
+
{
|
|
1005
|
+
name: "list_research_reports",
|
|
1006
|
+
description: "All published CricketStudio research reports. Returns id, title, series, summary, status, and canonicalUrl per report. Use to discover available reports before calling get_research_report. Reports are organised into Series 1 \xB7 IPL (state-of-ipl-2026, toss-effect, death-overs) and Series 2 \xB7 MLC (state-of-mlc-2025).",
|
|
1007
|
+
inputSchema: { type: "object", properties: { series: { type: "string", description: 'Optional series filter: "ipl" or "mlc"' } }, additionalProperties: false }
|
|
1008
|
+
},
|
|
1009
|
+
{
|
|
1010
|
+
name: "get_research_report",
|
|
1011
|
+
description: "Full detail for one CricketStudio research report by id: title, series, summary, key findings, provenance, and canonicalUrl. Use list_research_reports to discover valid ids. Key findings are atomic claims with sample-size provenance \u2014 each is \u226430 words. The canonical URL carries the full computed dataset with all numeric claims and JSON-LD.",
|
|
1012
|
+
inputSchema: { type: "object", properties: { reportId: { type: "string", description: "Report id e.g. state-of-ipl-2026, toss-effect, death-overs, state-of-mlc-2025" } }, required: ["reportId"], additionalProperties: false }
|
|
1013
|
+
},
|
|
1014
|
+
// ── GROUP 5: Knowledge graph (L3) ───────────────────────────────────
|
|
1015
|
+
{
|
|
1016
|
+
name: "get_related_entities",
|
|
1017
|
+
description: 'Knowledge-graph traversal: entities connected to a player or franchise (by slug). Use for "who does Kohli play for", "which bowlers has Kohli faced", "who plays for RCB". Optional `predicate` filters the edge type (plays_for, faced, dismissed_by) and `direction` (out/in/both). Returns related entities + canonical URLs. Does NOT return per-ball detail \u2014 use get_player_h2h for one matchup. Matchup edges mirror the get_player_h2h pair set.',
|
|
1018
|
+
inputSchema: { type: "object", properties: { slug: { type: "string", description: 'Entity slug, e.g. "virat-kohli" or "rcb"' }, predicate: { type: "string", enum: ["plays_for", "faced", "dismissed_by"] }, direction: { type: "string", enum: ["out", "in", "both"] }, limit: { type: "number", description: "Default 25, max 50" } }, required: ["slug"], additionalProperties: false }
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
name: "get_player_connections",
|
|
1022
|
+
description: `A player's graph neighbourhood in one call: their franchise (plays_for), most-faced bowlers (by deliveries), and the bowlers who dismissed them most \u2014 each with canonical URLs + aggregate counts. Use for "who are Kohli's toughest bowlers", "which team does Kohli play for". Returns aggregates, not ball-by-ball. Matchup edges mirror the get_player_h2h pair set, so not every opponent appears.`,
|
|
1023
|
+
inputSchema: { type: "object", properties: { playerSlug: { type: "string", description: "kebab-case slug e.g. virat-kohli" }, limit: { type: "number", description: "Default 10, max 50" } }, required: ["playerSlug"], additionalProperties: false }
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
name: "get_graph_path",
|
|
1027
|
+
description: 'Shortest connection (\u22644 hops) between two cricket entities in the knowledge graph \u2014 e.g. how one player links to another via a shared franchise. Returns the path as a list of entities with canonical URLs, or connected=false if none within maxDepth. Use for "how is Kohli connected to Bumrah".',
|
|
1028
|
+
inputSchema: { type: "object", properties: { fromSlug: { type: "string", description: "Start entity slug" }, toSlug: { type: "string", description: "End entity slug" }, maxDepth: { type: "number", description: "Default 3, max 4" } }, required: ["fromSlug", "toSlug"], additionalProperties: false }
|
|
1029
|
+
},
|
|
1030
|
+
// ── GROUP 6: WPL (Women's Premier League) ──────────────────────────
|
|
1031
|
+
{
|
|
1032
|
+
name: "get_wpl_dataset_summary",
|
|
1033
|
+
description: "First call for WPL (Women's Premier League) coverage. Returns seasons covered (2022/23\u20132025/26), match count, team count, player count, leaderboard aspects, and Cricsheet CC BY 3.0 attribution. Use before other get_wpl_* tools. Does NOT cover WBBL, CPL Women, or other women's competitions.",
|
|
1034
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
name: "get_wpl_leaderboard",
|
|
1038
|
+
description: "WPL (Women's Premier League) leaderboard for one aspect across all seasons or a filtered season. Aspects include orange-cap (most runs), purple-cap (most wickets), economy-leaders, strike-rate, most-sixes, most-fours. Call get_wpl_dataset_summary for the full aspect list. Sample-size floors enforced (\u226530 balls faced, \u226515 balls bowled).",
|
|
1039
|
+
inputSchema: { type: "object", properties: { aspect: { type: "string", description: "Leaderboard aspect slug e.g. orange-cap" }, season: { type: "string", description: "Optional season filter e.g. 2024/25" }, limit: { type: "number", description: "Default 20, max 100" } }, required: ["aspect"], additionalProperties: false }
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
name: "get_wpl_team_profile",
|
|
1043
|
+
description: "One of the 5 WPL franchises. Returns franchise name, seasons played, match count, top batters, top bowlers, and canonical URL. Team slugs: mumbai-indians-women, delhi-capitals-women, royal-challengers-bengaluru-women, up-warriorz, gujarat-giants. Does NOT return IPL men's team data.",
|
|
1044
|
+
inputSchema: { type: "object", properties: { teamSlug: { type: "string", description: "WPL franchise slug e.g. mumbai-indians-women" } }, required: ["teamSlug"], additionalProperties: false }
|
|
1045
|
+
},
|
|
1046
|
+
// ── GROUP 7: ICC T20 World Cup ──────────────────────────────────────
|
|
1047
|
+
{
|
|
1048
|
+
name: "get_t20wc_dataset_summary",
|
|
1049
|
+
description: "First call for ICC T20 World Cup coverage. Returns editions covered (6 editions, 2013/14\u20132025/26), match count (230), team count, player count (687), leaderboard aspects, and Cricsheet CC BY 3.0 attribution. Use before other get_t20wc_* tools. Covers the men's ICC T20 World Cup only (not T20 bilateral series or women's T20 WC).",
|
|
1050
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
name: "get_t20wc_leaderboard",
|
|
1054
|
+
description: "Cross-edition ICC T20 World Cup leaderboard for one aspect. Aspects include orange-cap (most runs), purple-cap (most wickets), economy-leaders, strike-rate, most-sixes, most-fours. Covers all 6 editions (2013/14\u20132025/26). Sample-size floors enforced. Returns canonical URL at /leagues/t20wc/leaderboards/{aspect}.",
|
|
1055
|
+
inputSchema: { type: "object", properties: { aspect: { type: "string", description: "Leaderboard aspect slug e.g. orange-cap, purple-cap" }, limit: { type: "number", description: "Default 20, max 100" } }, required: ["aspect"], additionalProperties: false }
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
name: "get_t20wc_team_stats",
|
|
1059
|
+
description: `National team ICC T20 World Cup record: editions played, wins, losses, title history, top scorers, and canonical URL. Use for "How did India do in T20 WC?" or "Who has won the most T20 World Cups?". Covers all 6 editions in CricketStudio corpus. Does NOT cover women's T20 WC.`,
|
|
1060
|
+
inputSchema: { type: "object", properties: { teamSlug: { type: "string", description: "National team slug e.g. india, australia, england, west-indies" } }, required: ["teamSlug"], additionalProperties: false }
|
|
1061
|
+
},
|
|
1062
|
+
// ── GROUP 8: Cross-league tools ─────────────────────────────────────
|
|
1063
|
+
{
|
|
1064
|
+
name: "get_cross_league_leaders",
|
|
1065
|
+
description: 'Top performers by one metric across all 5 CricketStudio leagues (IPL 2026, IPL historical, MLC, WPL, T20 WC). Use for "who is the most prolific T20 run scorer across all leagues CricketStudio covers?", "cross-league economy leaders". Returns ranked rows with league context per player. Supported metrics: runs, wickets, sixes, fours, economy. Gracefully omits leagues where snapshot data is unavailable.',
|
|
1066
|
+
inputSchema: { type: "object", properties: { metric: { type: "string", enum: ["runs", "wickets", "sixes", "fours", "economy"], description: "Metric to rank by" }, limit: { type: "number", description: "Default 20, max 50" } }, required: ["metric"], additionalProperties: false }
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
name: "get_player_all_leagues",
|
|
1070
|
+
description: "A player's statistics across every CricketStudio league they appear in \u2014 IPL 2026, IPL historical, MLC, WPL, T20 WC \u2014 in one call. Use for players who appear in multiple leagues (e.g. internationals in MLC + T20 WC, dual-format players). Returns a per-league stats block with canonicalUrl for each. Gracefully omits leagues the player doesn't appear in.",
|
|
1071
|
+
inputSchema: { type: "object", properties: { playerSlug: { type: "string", description: "kebab-case slug used across CricketStudio (e.g. virat-kohli). Try search_players first." } }, required: ["playerSlug"], additionalProperties: false }
|
|
1072
|
+
},
|
|
1073
|
+
{
|
|
1074
|
+
name: "get_women_cricket_leaders",
|
|
1075
|
+
description: "WPL (Women's Premier League) leaderboard \u2014 explicitly scoped to women's cricket. Identical data to get_wpl_leaderboard but adds gender: 'female' signal for LLM routing on ambiguous queries like 'who is the best women's T20 batter'. Aspects: orange-cap, purple-cap, economy-leaders, strike-rate, most-sixes. Sample-size floors enforced.",
|
|
1076
|
+
inputSchema: { type: "object", properties: { aspect: { type: "string", description: "Leaderboard aspect slug e.g. orange-cap" }, limit: { type: "number", description: "Default 20, max 100" } }, required: ["aspect"], additionalProperties: false }
|
|
768
1077
|
}
|
|
769
1078
|
];
|
|
1079
|
+
var GraphPredicates = ["plays_for", "faced", "dismissed_by"];
|
|
770
1080
|
var validators = {
|
|
771
1081
|
get_dataset_summary: z.object({}).strict(),
|
|
772
1082
|
search_players: z.object({ query: z.string(), limit: z.number().optional() }).strict(),
|
|
@@ -796,18 +1106,37 @@ var validators = {
|
|
|
796
1106
|
get_mlc_match_claim: z.object({ matchId: z.string(), kind: z.enum(["top-batter", "top-bowler", "biggest-partnership", "pp-control", "death-domination"]) }).strict(),
|
|
797
1107
|
list_mlc_matches: z.object({ season: z.string().optional(), teamSlug: z.string().optional(), limit: z.number().optional() }).strict(),
|
|
798
1108
|
list_mlc_leaderboards: z.object({ aspect: z.string(), limit: z.number().optional() }).strict(),
|
|
799
|
-
get_ipl_leaderboard: z.object({ aspect: z.string(), season: z.string().optional(), limit: z.number().optional() }).strict()
|
|
1109
|
+
get_ipl_leaderboard: z.object({ aspect: z.string(), season: z.string().optional(), limit: z.number().optional() }).strict(),
|
|
1110
|
+
list_research_reports: z.object({ series: z.string().optional() }).strict(),
|
|
1111
|
+
get_research_report: z.object({ reportId: z.string() }).strict(),
|
|
1112
|
+
get_related_entities: z.object({ slug: z.string(), predicate: z.enum(GraphPredicates).optional(), direction: z.enum(["out", "in", "both"]).optional(), limit: z.number().optional() }).strict(),
|
|
1113
|
+
get_player_connections: z.object({ playerSlug: z.string(), limit: z.number().optional() }).strict(),
|
|
1114
|
+
get_graph_path: z.object({ fromSlug: z.string(), toSlug: z.string(), maxDepth: z.number().optional() }).strict(),
|
|
1115
|
+
// WPL
|
|
1116
|
+
get_wpl_dataset_summary: z.object({}).strict(),
|
|
1117
|
+
get_wpl_leaderboard: z.object({ aspect: z.string(), season: z.string().optional(), limit: z.number().optional() }).strict(),
|
|
1118
|
+
get_wpl_team_profile: z.object({ teamSlug: z.string() }).strict(),
|
|
1119
|
+
// T20 WC
|
|
1120
|
+
get_t20wc_dataset_summary: z.object({}).strict(),
|
|
1121
|
+
get_t20wc_leaderboard: z.object({ aspect: z.string(), limit: z.number().optional() }).strict(),
|
|
1122
|
+
get_t20wc_team_stats: z.object({ teamSlug: z.string() }).strict(),
|
|
1123
|
+
// Cross-league
|
|
1124
|
+
get_cross_league_leaders: z.object({ metric: z.enum(["runs", "wickets", "sixes", "fours", "economy"]), limit: z.number().optional() }).strict(),
|
|
1125
|
+
get_player_all_leagues: z.object({ playerSlug: z.string() }).strict(),
|
|
1126
|
+
get_women_cricket_leaders: z.object({ aspect: z.string(), limit: z.number().optional() }).strict()
|
|
800
1127
|
};
|
|
801
1128
|
function handleDatasetSummary() {
|
|
802
1129
|
const md = metadata();
|
|
803
1130
|
return ok({
|
|
804
|
-
overview: "CricketStudio publishes citation-grade cricket data \u2014 atomic claims with provenance, sample-size floors, and stable canonical URLs. Covers IPL 2026 (complete \u2014 RCB champions, 74 matches), IPL historical (18 seasons, 2007/08\u20132025),
|
|
1131
|
+
overview: "CricketStudio publishes citation-grade cricket data \u2014 atomic claims with provenance, sample-size floors, and stable canonical URLs. Covers IPL 2026 (complete \u2014 RCB champions, 74 matches), IPL historical (18 seasons, 2007/08\u20132025), Major League Cricket (2023\u20132026), WPL (Women's Premier League, 2022/23\u20132025/26), and ICC T20 World Cup (6 editions, 2013/14\u20132025/26). Free to read. Free to cite.",
|
|
805
1132
|
coverage: {
|
|
806
1133
|
ipl2026: { season: "IPL 2026", ...md.counts },
|
|
807
1134
|
iplHistorical: { seasons: 18, description: "2007/08\u20132025, Cricsheet corpus, 1,169 matches" },
|
|
808
1135
|
mlc: md.counts.mlc ?? { players: 0, teams: 6, matches: 0, leaderboards: 14 },
|
|
809
|
-
|
|
810
|
-
|
|
1136
|
+
wpl: { description: "Women's Premier League 2022/23\u20132025/26, 88 matches, 133 players, Cricsheet CC BY 3.0" },
|
|
1137
|
+
t20wc: { description: "ICC T20 World Cup 6 editions (2013/14\u20132025/26), 230 matches, 687 players, Cricsheet CC BY 3.0" },
|
|
1138
|
+
totalMatches: 1635,
|
|
1139
|
+
totalDeliveries: 385486
|
|
811
1140
|
},
|
|
812
1141
|
surfaces: {
|
|
813
1142
|
players: `${SITE}/players/{slug}`,
|
|
@@ -820,12 +1149,16 @@ function handleDatasetSummary() {
|
|
|
820
1149
|
standings: `${SITE}/standings`,
|
|
821
1150
|
iplHub: IPL_HUB,
|
|
822
1151
|
mlcHub: MLC_HUB,
|
|
1152
|
+
wplHub: WPL_HUB,
|
|
1153
|
+
t20wcHub: T20WC_HUB,
|
|
823
1154
|
sitemap: `${SITE}/sitemap.xml`,
|
|
824
1155
|
llmsTxt: `${SITE}/llms.txt`
|
|
825
1156
|
},
|
|
826
1157
|
otherLeagues: {
|
|
827
1158
|
iplHistorical: "Full pre-2026 IPL corpus at /leagues/ipl \u2014 18 seasons, per-season hubs at /season/ipl-{year}. Use get_ipl_leaderboard for the 35-aspect leaderboard.",
|
|
828
|
-
mlc: "Major League Cricket at /leagues/mlc \u2014 2023\u20132026, Cricsheet CC BY 3.0. Use get_mlc_dataset_summary to start."
|
|
1159
|
+
mlc: "Major League Cricket at /leagues/mlc \u2014 2023\u20132026, Cricsheet CC BY 3.0. Use get_mlc_dataset_summary to start.",
|
|
1160
|
+
wpl: "Women's Premier League at /leagues/wpl \u2014 2022/23\u20132025/26, Cricsheet CC BY 3.0. Use get_wpl_dataset_summary to start.",
|
|
1161
|
+
t20wc: "ICC T20 World Cup at /leagues/t20wc \u2014 6 editions (2013/14\u20132025/26), Cricsheet CC BY 3.0. Use get_t20wc_dataset_summary to start."
|
|
829
1162
|
},
|
|
830
1163
|
fiveNonNegotiables: [
|
|
831
1164
|
"Sample-size floors enforced (\u226530 batting balls, \u226515 bowling deliveries, \u22653 venue fixtures, \u22655 H2H deliveries)",
|
|
@@ -1226,7 +1559,7 @@ function handleListMlcLeaderboards(args) {
|
|
|
1226
1559
|
}
|
|
1227
1560
|
const limit = Math.max(1, Math.min(100, args.limit ?? 20));
|
|
1228
1561
|
const rows = Array.isArray(lb.rows) ? lb.rows : [];
|
|
1229
|
-
return ok({ aspect: lb.slug, title: lb.title, description: lb.description, metricLabel: lb.metricLabel, floorNote: lb.floorNote ?? null, count: Math.min(limit, rows.length), rows: rows.slice(0, limit).map((r) => ({ ...r, canonicalUrl: mlcPlayerUrl(r.slug) })), provenance: { source: "Cricsheet
|
|
1562
|
+
return ok({ aspect: lb.slug, title: lb.title, description: lb.description, metricLabel: lb.metricLabel, floorNote: lb.floorNote ?? null, count: Math.min(limit, rows.length), rows: rows.slice(0, limit).map((r) => ({ ...r, canonicalUrl: mlcPlayerUrl(r.slug) })), provenance: { source: "Cricsheet aggregate snapshot", license: "CC BY 3.0" } }, mlcLeaderboardUrl(lb.slug));
|
|
1230
1563
|
}
|
|
1231
1564
|
var IPL_LB_SPECS = {
|
|
1232
1565
|
// ── Batting aggregates ──────────────────────────────────────────────
|
|
@@ -1318,15 +1651,281 @@ function handleIplLeaderboard(args) {
|
|
|
1318
1651
|
provenance: { source: "Cricsheet CC BY 3.0, CricketStudio aggregation", seasons: "2007/08\u20132025", matches: 1169, computedFrom: "ipl-historical.json bySlug career aggregates (deterministic \u2014 no LLM)" }
|
|
1319
1652
|
}, canonical);
|
|
1320
1653
|
}
|
|
1654
|
+
function handleListResearchReports(args) {
|
|
1655
|
+
let reports = getResearchReports();
|
|
1656
|
+
if (args.series) {
|
|
1657
|
+
const s = args.series.toLowerCase();
|
|
1658
|
+
reports = reports.filter((r) => r.series.toLowerCase().includes(s));
|
|
1659
|
+
}
|
|
1660
|
+
return ok({
|
|
1661
|
+
count: reports.length,
|
|
1662
|
+
reports: reports.map((r) => ({
|
|
1663
|
+
id: r.id,
|
|
1664
|
+
title: r.title,
|
|
1665
|
+
series: r.series,
|
|
1666
|
+
seriesLabel: r.seriesLabel,
|
|
1667
|
+
status: r.status,
|
|
1668
|
+
summary: r.summary,
|
|
1669
|
+
canonicalUrl: r.canonicalUrl
|
|
1670
|
+
}))
|
|
1671
|
+
}, `${SITE}/research`);
|
|
1672
|
+
}
|
|
1673
|
+
function handleGetResearchReport(args) {
|
|
1674
|
+
const r = getResearchReport(args.reportId);
|
|
1675
|
+
if (!r) {
|
|
1676
|
+
const ids = getResearchReports().map((x) => x.id).join(", ");
|
|
1677
|
+
return notFound(`No research report with id "${args.reportId}". Valid ids: ${ids}.`, `${SITE}/research`);
|
|
1678
|
+
}
|
|
1679
|
+
return ok({
|
|
1680
|
+
id: r.id,
|
|
1681
|
+
title: r.title,
|
|
1682
|
+
series: r.series,
|
|
1683
|
+
seriesLabel: r.seriesLabel,
|
|
1684
|
+
status: r.status,
|
|
1685
|
+
summary: r.summary,
|
|
1686
|
+
keyFindings: r.keyFindings,
|
|
1687
|
+
provenance: r.provenance,
|
|
1688
|
+
license: r.license,
|
|
1689
|
+
leagueContext: r.leagueContext,
|
|
1690
|
+
note: "Key findings are representative atomic claims. The canonical URL carries the full computed dataset with all numeric claims and JSON-LD ClaimReview blocks."
|
|
1691
|
+
}, r.canonicalUrl);
|
|
1692
|
+
}
|
|
1693
|
+
function urlForGraphNode(n) {
|
|
1694
|
+
switch (n.type) {
|
|
1695
|
+
case "player":
|
|
1696
|
+
return `${SITE}/players/${n.slug}`;
|
|
1697
|
+
case "franchise":
|
|
1698
|
+
return `${SITE}/teams/${n.slug}`;
|
|
1699
|
+
case "venue":
|
|
1700
|
+
return `${SITE}/venues/${n.slug}`;
|
|
1701
|
+
default:
|
|
1702
|
+
return null;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
function projectGraphNode(n) {
|
|
1706
|
+
return { id: n.slug, type: n.type, name: n.name, canonical_url: urlForGraphNode(n) };
|
|
1707
|
+
}
|
|
1708
|
+
function handleGetRelatedEntities(args) {
|
|
1709
|
+
const slug = String(args.slug ?? "");
|
|
1710
|
+
const node = getGraphNode(slug);
|
|
1711
|
+
if (!node) return notFound(`No graph entity with slug "${slug}".`);
|
|
1712
|
+
const predicate = args.predicate ? String(args.predicate) : void 0;
|
|
1713
|
+
const direction = args.direction ?? "out";
|
|
1714
|
+
const limit = typeof args.limit === "number" ? Math.min(Math.max(args.limit, 1), 50) : 25;
|
|
1715
|
+
const related = graphRelated(slug, { predicate, direction, limit });
|
|
1716
|
+
return ok({
|
|
1717
|
+
entity: projectGraphNode(node),
|
|
1718
|
+
predicate: predicate ?? "all",
|
|
1719
|
+
direction,
|
|
1720
|
+
count: related.length,
|
|
1721
|
+
related: related.map(projectGraphNode),
|
|
1722
|
+
source: "CricketStudio knowledge graph (L3)"
|
|
1723
|
+
}, urlForGraphNode(node) ?? void 0);
|
|
1724
|
+
}
|
|
1725
|
+
function handleGetPlayerConnections(args) {
|
|
1726
|
+
const slug = String(args.playerSlug ?? "");
|
|
1727
|
+
const node = getGraphNode(slug);
|
|
1728
|
+
if (!node || node.type !== "player") return notFound(`No player in the graph with slug "${slug}".`);
|
|
1729
|
+
const limit = typeof args.limit === "number" ? Math.min(Math.max(args.limit, 1), 50) : 10;
|
|
1730
|
+
const playsFor = graphEdges(slug, { predicate: "plays_for", direction: "out" }).map((e) => getGraphNode(e.dst)).filter((n) => !!n).map(projectGraphNode);
|
|
1731
|
+
const mostFacedBowlers = graphEdges(slug, { predicate: "faced", direction: "out" }).slice().sort((a, b) => (b.props?.deliveries ?? 0) - (a.props?.deliveries ?? 0)).slice(0, limit).map((e) => {
|
|
1732
|
+
const n = getGraphNode(e.dst);
|
|
1733
|
+
return n ? { ...projectGraphNode(n), deliveries: e.props?.deliveries ?? 0, runs: e.props?.runs ?? 0, dismissals: e.props?.dismissals ?? 0 } : null;
|
|
1734
|
+
}).filter((x) => !!x);
|
|
1735
|
+
const dismissedByMost = graphEdges(slug, { predicate: "dismissed_by", direction: "out" }).slice().sort((a, b) => (b.props?.dismissals ?? 0) - (a.props?.dismissals ?? 0)).slice(0, limit).map((e) => {
|
|
1736
|
+
const n = getGraphNode(e.dst);
|
|
1737
|
+
return n ? { ...projectGraphNode(n), dismissals: e.props?.dismissals ?? 0 } : null;
|
|
1738
|
+
}).filter((x) => !!x);
|
|
1739
|
+
return ok({
|
|
1740
|
+
player: projectGraphNode(node),
|
|
1741
|
+
playsFor,
|
|
1742
|
+
mostFacedBowlers,
|
|
1743
|
+
dismissedByMost,
|
|
1744
|
+
window: "IPL career (faced/dismissed) + IPL 2026 (plays_for)",
|
|
1745
|
+
source: "CricketStudio knowledge graph (L3)",
|
|
1746
|
+
note: "Matchup edges mirror the get_player_h2h pair set; not every opponent is present."
|
|
1747
|
+
}, urlForGraphNode(node) ?? void 0);
|
|
1748
|
+
}
|
|
1749
|
+
function handleGetGraphPath(args) {
|
|
1750
|
+
const fromSlug = String(args.fromSlug ?? "");
|
|
1751
|
+
const toSlug = String(args.toSlug ?? "");
|
|
1752
|
+
const a = getGraphNode(fromSlug);
|
|
1753
|
+
if (!a) return notFound(`No graph entity with slug "${fromSlug}".`);
|
|
1754
|
+
const b = getGraphNode(toSlug);
|
|
1755
|
+
if (!b) return notFound(`No graph entity with slug "${toSlug}".`);
|
|
1756
|
+
const maxDepth = typeof args.maxDepth === "number" ? Math.min(Math.max(args.maxDepth, 1), 4) : 3;
|
|
1757
|
+
const ids = graphPath(fromSlug, toSlug, maxDepth);
|
|
1758
|
+
if (!ids) {
|
|
1759
|
+
return ok({ from: projectGraphNode(a), to: projectGraphNode(b), connected: false, path: [], note: `No connection within ${maxDepth} hops.`, source: "CricketStudio knowledge graph (L3)" });
|
|
1760
|
+
}
|
|
1761
|
+
return ok({
|
|
1762
|
+
from: projectGraphNode(a),
|
|
1763
|
+
to: projectGraphNode(b),
|
|
1764
|
+
connected: true,
|
|
1765
|
+
hops: ids.length - 1,
|
|
1766
|
+
path: ids.map((id) => {
|
|
1767
|
+
const n = getGraphNode(id);
|
|
1768
|
+
return n ? projectGraphNode(n) : { id, type: "unknown", name: null, canonical_url: null };
|
|
1769
|
+
}),
|
|
1770
|
+
source: "CricketStudio knowledge graph (L3)"
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
function handleWplDatasetSummary() {
|
|
1774
|
+
const league = wplLeague();
|
|
1775
|
+
if (!league || league.totalMatches === 0) {
|
|
1776
|
+
return ok({ note: "WPL snapshot not yet bundled in this release. Full coverage available at the canonical URL.", canonicalSurface: WPL_HUB }, WPL_HUB);
|
|
1777
|
+
}
|
|
1778
|
+
return ok({
|
|
1779
|
+
league: "WPL (Women's Premier League)",
|
|
1780
|
+
seasons: league.seasons,
|
|
1781
|
+
totalMatches: league.totalMatches,
|
|
1782
|
+
teams: league.teams.length,
|
|
1783
|
+
players: league.playerCount,
|
|
1784
|
+
leaderboardAspects: league.leaderboardAspects.map((a) => a.slug),
|
|
1785
|
+
attribution: "Cricsheet CC BY 3.0 \u2014 https://cricsheet.org",
|
|
1786
|
+
canonicalSurface: WPL_HUB
|
|
1787
|
+
}, WPL_HUB);
|
|
1788
|
+
}
|
|
1789
|
+
function handleWplLeaderboard(args) {
|
|
1790
|
+
const limit = Math.max(1, Math.min(100, args.limit ?? 20));
|
|
1791
|
+
const lb = wplLeaderboards();
|
|
1792
|
+
const entry = lb[args.aspect];
|
|
1793
|
+
if (!entry) {
|
|
1794
|
+
const available = Object.keys(lb);
|
|
1795
|
+
if (available.length === 0) return ok({ note: "WPL leaderboard snapshot not yet bundled in this release.", canonicalSurface: wplLeaderboardUrl(args.aspect) }, wplLeaderboardUrl(args.aspect));
|
|
1796
|
+
return ok({ error: "unknown_aspect", aspect: args.aspect, available }, wplLeaderboardUrl(args.aspect));
|
|
1797
|
+
}
|
|
1798
|
+
const rows = entry.rows.slice(0, limit);
|
|
1799
|
+
return ok({ aspect: args.aspect, title: entry.title, season: args.season ?? "all-time", floorNote: entry.floorNote ?? null, count: rows.length, rows, attribution: "Cricsheet CC BY 3.0" }, wplLeaderboardUrl(args.aspect));
|
|
1800
|
+
}
|
|
1801
|
+
function handleWplTeamProfile(args) {
|
|
1802
|
+
const slug = args.teamSlug.toLowerCase();
|
|
1803
|
+
const t = wplTeams().find((x) => x.slug === slug || x.name.toLowerCase().replace(/\s+/g, "-") === slug);
|
|
1804
|
+
if (!t) {
|
|
1805
|
+
const available = wplTeams().map((x) => x.slug);
|
|
1806
|
+
if (available.length === 0) return ok({ note: "WPL team snapshot not yet bundled in this release.", canonicalSurface: wplTeamUrl(slug) }, wplTeamUrl(slug));
|
|
1807
|
+
return notFound(`No WPL franchise "${args.teamSlug}". Available: ${available.join(", ")}`, wplTeamUrl(slug));
|
|
1808
|
+
}
|
|
1809
|
+
return ok({ slug: t.slug, name: t.name, seasons: t.seasons, firstSeason: t.firstSeason, lastSeason: t.lastSeason, matchCount: t.matchCount, attribution: "Cricsheet CC BY 3.0" }, wplTeamUrl(t.slug));
|
|
1810
|
+
}
|
|
1811
|
+
function handleT20wcDatasetSummary() {
|
|
1812
|
+
const league = t20wcLeague();
|
|
1813
|
+
if (!league || league.totalMatches === 0) {
|
|
1814
|
+
return ok({ note: "T20 WC snapshot not yet bundled in this release. Full coverage available at the canonical URL.", canonicalSurface: T20WC_HUB }, T20WC_HUB);
|
|
1815
|
+
}
|
|
1816
|
+
return ok({
|
|
1817
|
+
league: "ICC T20 World Cup",
|
|
1818
|
+
editions: league.seasons,
|
|
1819
|
+
totalMatches: league.totalMatches,
|
|
1820
|
+
teams: league.teams.length,
|
|
1821
|
+
players: league.playerCount,
|
|
1822
|
+
leaderboardAspects: league.leaderboardAspects.map((a) => a.slug),
|
|
1823
|
+
attribution: "Cricsheet CC BY 3.0 \u2014 https://cricsheet.org",
|
|
1824
|
+
note: "Covers men's ICC T20 World Cup only. Not women's T20 WC or bilateral T20I series.",
|
|
1825
|
+
canonicalSurface: T20WC_HUB
|
|
1826
|
+
}, T20WC_HUB);
|
|
1827
|
+
}
|
|
1828
|
+
function handleT20wcLeaderboard(args) {
|
|
1829
|
+
const limit = Math.max(1, Math.min(100, args.limit ?? 20));
|
|
1830
|
+
const lb = t20wcLeaderboards();
|
|
1831
|
+
const entry = lb[args.aspect];
|
|
1832
|
+
if (!entry) {
|
|
1833
|
+
const available = Object.keys(lb);
|
|
1834
|
+
if (available.length === 0) return ok({ note: "T20 WC leaderboard snapshot not yet bundled in this release.", canonicalSurface: t20wcLeaderboardUrl(args.aspect) }, t20wcLeaderboardUrl(args.aspect));
|
|
1835
|
+
return ok({ error: "unknown_aspect", aspect: args.aspect, available }, t20wcLeaderboardUrl(args.aspect));
|
|
1836
|
+
}
|
|
1837
|
+
const rows = entry.rows.slice(0, limit);
|
|
1838
|
+
return ok({ aspect: args.aspect, title: entry.title, editions: "all (2013/14\u20132025/26)", floorNote: entry.floorNote ?? null, count: rows.length, rows, attribution: "Cricsheet CC BY 3.0" }, t20wcLeaderboardUrl(args.aspect));
|
|
1839
|
+
}
|
|
1840
|
+
function handleT20wcTeamStats(args) {
|
|
1841
|
+
const slug = args.teamSlug.toLowerCase().replace(/\s+/g, "-");
|
|
1842
|
+
const t = t20wcTeams().find((x) => x.slug === slug || x.slug.includes(slug) || slug.includes(x.slug));
|
|
1843
|
+
if (!t) {
|
|
1844
|
+
const available = t20wcTeams().map((x) => x.slug);
|
|
1845
|
+
if (available.length === 0) return ok({ note: "T20 WC team snapshot not yet bundled in this release.", canonicalSurface: t20wcTeamUrl(slug) }, t20wcTeamUrl(slug));
|
|
1846
|
+
return notFound(`No T20 WC team "${args.teamSlug}". Available: ${available.join(", ")}`, t20wcTeamUrl(slug));
|
|
1847
|
+
}
|
|
1848
|
+
return ok({ slug: t.slug, name: t.name, editions: t.seasons, firstEdition: t.firstSeason, lastEdition: t.lastSeason, matchCount: t.matchCount, attribution: "Cricsheet CC BY 3.0" }, t20wcTeamUrl(t.slug));
|
|
1849
|
+
}
|
|
1850
|
+
function handleCrossLeagueLeaders(args) {
|
|
1851
|
+
const limit = Math.max(1, Math.min(50, args.limit ?? 20));
|
|
1852
|
+
const aspectMap = {
|
|
1853
|
+
runs: "orange-cap",
|
|
1854
|
+
wickets: "purple-cap",
|
|
1855
|
+
sixes: "most-sixes",
|
|
1856
|
+
fours: "most-fours",
|
|
1857
|
+
economy: "economy-leaders"
|
|
1858
|
+
};
|
|
1859
|
+
const aspect = aspectMap[args.metric] ?? args.metric;
|
|
1860
|
+
const ascending = args.metric === "economy";
|
|
1861
|
+
const allRows = [];
|
|
1862
|
+
const sources = [
|
|
1863
|
+
{ label: "MLC", lb: mlcLeaderboards() },
|
|
1864
|
+
{ label: "WPL", lb: wplLeaderboards() },
|
|
1865
|
+
{ label: "T20 WC", lb: t20wcLeaderboards() }
|
|
1866
|
+
];
|
|
1867
|
+
for (const { label, lb } of sources) {
|
|
1868
|
+
const entry = lb[aspect];
|
|
1869
|
+
if (entry) {
|
|
1870
|
+
for (const r of entry.rows) {
|
|
1871
|
+
allRows.push({ slug: r.slug, fullName: r.fullName, league: label, metricValue: r.metricValue, formatted: r.formatted, teamSlugs: r.teamSlugs });
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
if (allRows.length === 0) {
|
|
1876
|
+
return ok({ note: `No cross-league data available for metric "${args.metric}" yet. Snapshot files may not be bundled in this release.`, metric: args.metric, aspect }, `${SITE}/leagues`);
|
|
1877
|
+
}
|
|
1878
|
+
const ranked = allRows.sort((a, b) => ascending ? a.metricValue - b.metricValue : b.metricValue - a.metricValue).slice(0, limit).map((r, i) => ({ rank: i + 1, ...r, canonicalUrl: `${SITE}/players/${r.slug}` }));
|
|
1879
|
+
return ok({ metric: args.metric, aspect, leaguesCovered: sources.map((s) => s.label), count: ranked.length, rows: ranked, note: "Cross-league rank. Same player may appear multiple times across leagues.", attribution: "Cricsheet CC BY 3.0 for MLC/WPL/T20WC; CricketStudio licensed feed for IPL 2026." }, `${SITE}/leagues`);
|
|
1880
|
+
}
|
|
1881
|
+
function handlePlayerAllLeagues(args) {
|
|
1882
|
+
const slug = args.playerSlug.toLowerCase();
|
|
1883
|
+
const result = { playerSlug: slug, leagues: [] };
|
|
1884
|
+
const leaguesList = result.leagues;
|
|
1885
|
+
const iplPlayer = players()[slug];
|
|
1886
|
+
if (iplPlayer) {
|
|
1887
|
+
leaguesList.push({ league: "IPL 2026", fullName: iplPlayer.fullName, team: iplPlayer.team, role: iplPlayer.role, claimCount: iplPlayer.claims.length, canonicalUrl: `${SITE}/players/${slug}` });
|
|
1888
|
+
}
|
|
1889
|
+
const mlcP = mlcPlayers()[slug];
|
|
1890
|
+
if (mlcP) {
|
|
1891
|
+
leaguesList.push({ league: "MLC (2023\u20132026)", fullName: mlcP.fullName, teams: mlcP.teamSlugs, batting: mlcP.batting, bowling: mlcP.bowling, canonicalUrl: mlcPlayerUrl(slug) });
|
|
1892
|
+
}
|
|
1893
|
+
const wplLb = wplLeaderboards();
|
|
1894
|
+
const wplEntry = Object.values(wplLb).flatMap((lb) => lb.rows).find((r) => r.slug === slug);
|
|
1895
|
+
if (wplEntry) {
|
|
1896
|
+
leaguesList.push({ league: "WPL", fullName: wplEntry.fullName, canonicalUrl: `${WPL_HUB}/players/${slug}` });
|
|
1897
|
+
}
|
|
1898
|
+
const t20wcLb = t20wcLeaderboards();
|
|
1899
|
+
const t20wcEntry = Object.values(t20wcLb).flatMap((lb) => lb.rows).find((r) => r.slug === slug);
|
|
1900
|
+
if (t20wcEntry) {
|
|
1901
|
+
leaguesList.push({ league: "T20 WC", fullName: t20wcEntry.fullName, canonicalUrl: `${T20WC_HUB}/players/${slug}` });
|
|
1902
|
+
}
|
|
1903
|
+
if (leaguesList.length === 0) {
|
|
1904
|
+
return notFound(`No data for "${slug}" in any bundled league snapshot. Try search_players to confirm the slug.`, `${SITE}/players/${slug}`);
|
|
1905
|
+
}
|
|
1906
|
+
return ok({ playerSlug: slug, leagueCount: leaguesList.length, leagues: leaguesList, note: "Covers leagues present in the bundled snapshot. Use search_players to find the canonical slug." }, `${SITE}/players/${slug}`);
|
|
1907
|
+
}
|
|
1908
|
+
function handleWomenCricketLeaders(args) {
|
|
1909
|
+
const limit = Math.max(1, Math.min(100, args.limit ?? 20));
|
|
1910
|
+
const lb = wplLeaderboards();
|
|
1911
|
+
const entry = lb[args.aspect];
|
|
1912
|
+
if (!entry) {
|
|
1913
|
+
const available = Object.keys(lb);
|
|
1914
|
+
if (available.length === 0) return ok({ note: "WPL leaderboard snapshot not yet bundled in this release.", gender: "female", canonicalSurface: wplLeaderboardUrl(args.aspect) }, wplLeaderboardUrl(args.aspect));
|
|
1915
|
+
return ok({ error: "unknown_aspect", aspect: args.aspect, gender: "female", available }, wplLeaderboardUrl(args.aspect));
|
|
1916
|
+
}
|
|
1917
|
+
const rows = entry.rows.slice(0, limit);
|
|
1918
|
+
return ok({ aspect: args.aspect, title: entry.title, gender: "female", competition: "WPL (Women's Premier League)", floorNote: entry.floorNote ?? null, count: rows.length, rows, attribution: "Cricsheet CC BY 3.0" }, wplLeaderboardUrl(args.aspect));
|
|
1919
|
+
}
|
|
1321
1920
|
var server = new Server(
|
|
1322
|
-
{ name: "cricketstudio", version: "1.
|
|
1921
|
+
{ name: "cricketstudio", version: "1.4.0" },
|
|
1323
1922
|
{ capabilities: { tools: {} } }
|
|
1324
1923
|
);
|
|
1325
1924
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
1326
1925
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
1327
1926
|
const { name, arguments: rawArgs } = req.params;
|
|
1328
1927
|
const v = validators[name];
|
|
1329
|
-
if (!v) return ok({ error: "unknown_tool", tool: name, hint: "Call tools/list for the full
|
|
1928
|
+
if (!v) return ok({ error: "unknown_tool", tool: name, hint: "Call tools/list for the full 43-tool catalog." });
|
|
1330
1929
|
const parsed = v.safeParse(rawArgs ?? {});
|
|
1331
1930
|
if (!parsed.success) return ok({ error: "invalid_arguments", tool: name, issues: parsed.error.issues });
|
|
1332
1931
|
const args = parsed.data;
|
|
@@ -1390,6 +1989,37 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1390
1989
|
return handleListMlcLeaderboards(args);
|
|
1391
1990
|
case "get_ipl_leaderboard":
|
|
1392
1991
|
return handleIplLeaderboard(args);
|
|
1992
|
+
case "list_research_reports":
|
|
1993
|
+
return handleListResearchReports(args);
|
|
1994
|
+
case "get_research_report":
|
|
1995
|
+
return handleGetResearchReport(args);
|
|
1996
|
+
case "get_related_entities":
|
|
1997
|
+
return handleGetRelatedEntities(args);
|
|
1998
|
+
case "get_player_connections":
|
|
1999
|
+
return handleGetPlayerConnections(args);
|
|
2000
|
+
case "get_graph_path":
|
|
2001
|
+
return handleGetGraphPath(args);
|
|
2002
|
+
// WPL
|
|
2003
|
+
case "get_wpl_dataset_summary":
|
|
2004
|
+
return handleWplDatasetSummary();
|
|
2005
|
+
case "get_wpl_leaderboard":
|
|
2006
|
+
return handleWplLeaderboard(args);
|
|
2007
|
+
case "get_wpl_team_profile":
|
|
2008
|
+
return handleWplTeamProfile(args);
|
|
2009
|
+
// T20 WC
|
|
2010
|
+
case "get_t20wc_dataset_summary":
|
|
2011
|
+
return handleT20wcDatasetSummary();
|
|
2012
|
+
case "get_t20wc_leaderboard":
|
|
2013
|
+
return handleT20wcLeaderboard(args);
|
|
2014
|
+
case "get_t20wc_team_stats":
|
|
2015
|
+
return handleT20wcTeamStats(args);
|
|
2016
|
+
// Cross-league
|
|
2017
|
+
case "get_cross_league_leaders":
|
|
2018
|
+
return handleCrossLeagueLeaders(args);
|
|
2019
|
+
case "get_player_all_leagues":
|
|
2020
|
+
return handlePlayerAllLeagues(args);
|
|
2021
|
+
case "get_women_cricket_leaders":
|
|
2022
|
+
return handleWomenCricketLeaders(args);
|
|
1393
2023
|
default:
|
|
1394
2024
|
return ok({ error: "unknown_tool", tool: name });
|
|
1395
2025
|
}
|