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/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 SETU 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).",
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 SETU canonical snapshot.",
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, 14 leaderboard aspects, and Cricsheet CC BY 3.0 attribution. MLC is distinct from IPL and lives under /leagues/mlc.",
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. 14 aspects include: orange-cap, purple-cap, strike-rate, economy-leaders, most-sixes, most-fours, top-knocks, best-bowling, powerplay-strike-rate, death-overs-economy. Call get_mlc_dataset_summary for the full aspect list. Sample-size floors enforced.",
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). 35+ aspects: orange-cap, purple-cap, most-sixes, most-fours, strike-rate, economy-leaders, most-matches, most-fifties, most-hundreds, best-bowling-avg, most-ducks, powerplay-economy, death-sr, and per-season variants. Pass season to scope to one year (e.g. "ipl-2024"). Returns canonical URL at /leagues/ipl/leaderboards/{aspect}.',
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), and Major League Cricket (2023\u20132026). Free to read. Free to cite.",
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
- totalMatches: 1307,
810
- totalDeliveries: 309992
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 SETU snapshot", license: "CC BY 3.0" } }, mlcLeaderboardUrl(lb.slug));
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.0.0" },
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 29-tool catalog." });
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
  }