cricketstudio-mcp 1.0.2 → 1.2.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.
@@ -1,144 +1,144 @@
1
- [
2
- {
3
- "slug": "narendra-modi-stadium",
4
- "name": "Narendra Modi Stadium",
5
- "city": "Ahmedabad",
6
- "matchCount": 8,
7
- "geo": {
8
- "latitude": 23.0917,
9
- "longitude": 72.5972,
10
- "wikidataQid": "Q379935"
11
- }
12
- },
13
- {
14
- "slug": "wankhede-stadium",
15
- "name": "Wankhede Stadium",
16
- "city": "Mumbai",
17
- "matchCount": 7,
18
- "geo": {
19
- "latitude": 18.9388,
20
- "longitude": 72.8259,
21
- "wikidataQid": "Q1067595"
22
- }
23
- },
24
- {
25
- "slug": "bharat-ratna-shri-atal-bihari-vajpayee-ekana-cricket-stadium",
26
- "name": "Bharat Ratna Shri Atal Bihari Vajpayee Ekana Cricket Stadium",
27
- "city": "Lucknow",
28
- "matchCount": 7,
29
- "geo": {
30
- "latitude": 26.8467,
31
- "longitude": 80.9462,
32
- "wikidataQid": "Q26257057"
33
- }
34
- },
35
- {
36
- "slug": "arun-jaitley-stadium",
37
- "name": "Arun Jaitley Stadium",
38
- "city": "Delhi",
39
- "matchCount": 7,
40
- "geo": {
41
- "latitude": 28.6379,
42
- "longitude": 77.2425,
43
- "wikidataQid": "Q582518"
44
- }
45
- },
46
- {
47
- "slug": "rajiv-gandhi-international-stadium",
48
- "name": "Rajiv Gandhi International Stadium",
49
- "city": "Hyderabad",
50
- "matchCount": 7,
51
- "geo": {
52
- "latitude": 17.4068,
53
- "longitude": 78.55,
54
- "wikidataQid": "Q1419486"
55
- }
56
- },
57
- {
58
- "slug": "eden-gardens",
59
- "name": "Eden Gardens",
60
- "city": "Kolkata",
61
- "matchCount": 6,
62
- "geo": {
63
- "latitude": 22.5645,
64
- "longitude": 88.3433,
65
- "wikidataQid": "Q582510"
66
- }
67
- },
68
- {
69
- "slug": "ma-chidambaram-stadium",
70
- "name": "MA Chidambaram Stadium",
71
- "city": "Chennai",
72
- "matchCount": 6,
73
- "geo": {
74
- "latitude": 13.0628,
75
- "longitude": 80.2792,
76
- "wikidataQid": "Q582488"
77
- }
78
- },
79
- {
80
- "slug": "m-chinnaswamy-stadium",
81
- "name": "M.Chinnaswamy Stadium",
82
- "city": "Bengaluru",
83
- "matchCount": 5,
84
- "geo": {
85
- "latitude": 12.9789,
86
- "longitude": 77.5993,
87
- "wikidataQid": "Q1135028"
88
- }
89
- },
90
- {
91
- "slug": "maharaja-yadavindra-singh-international-cricket-stadium-mullanpu",
92
- "name": "Maharaja Yadavindra Singh International Cricket Stadium, Mullanpur",
93
- "city": "Chandigarh",
94
- "matchCount": 4,
95
- "geo": {
96
- "latitude": 30.776,
97
- "longitude": 76.7012
98
- }
99
- },
100
- {
101
- "slug": "sawai-mansingh-stadium",
102
- "name": "Sawai Mansingh Stadium",
103
- "city": "Jaipur",
104
- "matchCount": 4,
105
- "geo": {
106
- "latitude": 26.8867,
107
- "longitude": 75.8067,
108
- "wikidataQid": "Q1437432"
109
- }
110
- },
111
- {
112
- "slug": "barsapara-cricket-stadium",
113
- "name": "Barsapara Cricket Stadium",
114
- "city": "Guwahati",
115
- "matchCount": 3,
116
- "geo": {
117
- "latitude": 26.1305,
118
- "longitude": 91.7966,
119
- "wikidataQid": "Q24938094"
120
- }
121
- },
122
- {
123
- "slug": "himachal-pradesh-cricket-association-stadium",
124
- "name": "Himachal Pradesh Cricket Association Stadium",
125
- "city": "Dharamsala",
126
- "matchCount": 3,
127
- "geo": {
128
- "latitude": 32.2393,
129
- "longitude": 76.3186,
130
- "wikidataQid": "Q5762830"
131
- }
132
- },
133
- {
134
- "slug": "shaheed-veer-narayan-singh-international-stadium",
135
- "name": "Shaheed Veer Narayan Singh International Stadium",
136
- "city": "Raipur",
137
- "matchCount": 2,
138
- "geo": {
139
- "latitude": 21.2514,
140
- "longitude": 81.6296,
141
- "wikidataQid": "Q7461193"
142
- }
143
- }
1
+ [
2
+ {
3
+ "slug": "narendra-modi-stadium",
4
+ "name": "Narendra Modi Stadium",
5
+ "city": "Ahmedabad",
6
+ "matchCount": 9,
7
+ "geo": {
8
+ "latitude": 23.0917,
9
+ "longitude": 72.5972,
10
+ "wikidataQid": "Q379935"
11
+ }
12
+ },
13
+ {
14
+ "slug": "wankhede-stadium",
15
+ "name": "Wankhede Stadium",
16
+ "city": "Mumbai",
17
+ "matchCount": 7,
18
+ "geo": {
19
+ "latitude": 18.9388,
20
+ "longitude": 72.8259,
21
+ "wikidataQid": "Q1067595"
22
+ }
23
+ },
24
+ {
25
+ "slug": "bharat-ratna-shri-atal-bihari-vajpayee-ekana-cricket-stadium",
26
+ "name": "Bharat Ratna Shri Atal Bihari Vajpayee Ekana Cricket Stadium",
27
+ "city": "Lucknow",
28
+ "matchCount": 7,
29
+ "geo": {
30
+ "latitude": 26.8467,
31
+ "longitude": 80.9462,
32
+ "wikidataQid": "Q26257057"
33
+ }
34
+ },
35
+ {
36
+ "slug": "arun-jaitley-stadium",
37
+ "name": "Arun Jaitley Stadium",
38
+ "city": "Delhi",
39
+ "matchCount": 7,
40
+ "geo": {
41
+ "latitude": 28.6379,
42
+ "longitude": 77.2425,
43
+ "wikidataQid": "Q582518"
44
+ }
45
+ },
46
+ {
47
+ "slug": "rajiv-gandhi-international-stadium",
48
+ "name": "Rajiv Gandhi International Stadium",
49
+ "city": "Hyderabad",
50
+ "matchCount": 7,
51
+ "geo": {
52
+ "latitude": 17.4068,
53
+ "longitude": 78.55,
54
+ "wikidataQid": "Q1419486"
55
+ }
56
+ },
57
+ {
58
+ "slug": "maharaja-yadavindra-singh-international-cricket-stadium-mullanpu",
59
+ "name": "Maharaja Yadavindra Singh International Cricket Stadium, Mullanpur",
60
+ "city": "Chandigarh",
61
+ "matchCount": 6,
62
+ "geo": {
63
+ "latitude": 30.776,
64
+ "longitude": 76.7012
65
+ }
66
+ },
67
+ {
68
+ "slug": "eden-gardens",
69
+ "name": "Eden Gardens",
70
+ "city": "Kolkata",
71
+ "matchCount": 6,
72
+ "geo": {
73
+ "latitude": 22.5645,
74
+ "longitude": 88.3433,
75
+ "wikidataQid": "Q582510"
76
+ }
77
+ },
78
+ {
79
+ "slug": "ma-chidambaram-stadium",
80
+ "name": "MA Chidambaram Stadium",
81
+ "city": "Chennai",
82
+ "matchCount": 6,
83
+ "geo": {
84
+ "latitude": 13.0628,
85
+ "longitude": 80.2792,
86
+ "wikidataQid": "Q582488"
87
+ }
88
+ },
89
+ {
90
+ "slug": "m-chinnaswamy-stadium",
91
+ "name": "M.Chinnaswamy Stadium",
92
+ "city": "Bengaluru",
93
+ "matchCount": 5,
94
+ "geo": {
95
+ "latitude": 12.9789,
96
+ "longitude": 77.5993,
97
+ "wikidataQid": "Q1135028"
98
+ }
99
+ },
100
+ {
101
+ "slug": "sawai-mansingh-stadium",
102
+ "name": "Sawai Mansingh Stadium",
103
+ "city": "Jaipur",
104
+ "matchCount": 4,
105
+ "geo": {
106
+ "latitude": 26.8867,
107
+ "longitude": 75.8067,
108
+ "wikidataQid": "Q1437432"
109
+ }
110
+ },
111
+ {
112
+ "slug": "himachal-pradesh-cricket-association-stadium",
113
+ "name": "Himachal Pradesh Cricket Association Stadium",
114
+ "city": "Dharamsala",
115
+ "matchCount": 4,
116
+ "geo": {
117
+ "latitude": 32.2393,
118
+ "longitude": 76.3186,
119
+ "wikidataQid": "Q5762830"
120
+ }
121
+ },
122
+ {
123
+ "slug": "barsapara-cricket-stadium",
124
+ "name": "Barsapara Cricket Stadium",
125
+ "city": "Guwahati",
126
+ "matchCount": 3,
127
+ "geo": {
128
+ "latitude": 26.1305,
129
+ "longitude": 91.7966,
130
+ "wikidataQid": "Q24938094"
131
+ }
132
+ },
133
+ {
134
+ "slug": "shaheed-veer-narayan-singh-international-stadium",
135
+ "name": "Shaheed Veer Narayan Singh International Stadium",
136
+ "city": "Raipur",
137
+ "matchCount": 2,
138
+ "geo": {
139
+ "latitude": 21.2514,
140
+ "longitude": 81.6296,
141
+ "wikidataQid": "Q7461193"
142
+ }
143
+ }
144
144
  ]
package/dist/index.js CHANGED
@@ -163,6 +163,84 @@ function getMatches(limit) {
163
163
  function getSeasonStats() {
164
164
  return loadSeasonStats();
165
165
  }
166
+ var _graphNodes = null;
167
+ var _graphOut = null;
168
+ var _graphIn = null;
169
+ function loadGraph() {
170
+ if (_graphNodes !== null) return;
171
+ _graphNodes = /* @__PURE__ */ new Map();
172
+ _graphOut = /* @__PURE__ */ new Map();
173
+ _graphIn = /* @__PURE__ */ new Map();
174
+ const doc = readJson("graph.json");
175
+ if (!doc) return;
176
+ for (const [slug, n] of Object.entries(doc.nodes ?? {})) {
177
+ _graphNodes.set(slug, { slug, type: n.type, name: n.name ?? null });
178
+ }
179
+ for (const e of doc.edges ?? []) {
180
+ if (!_graphOut.has(e.src)) _graphOut.set(e.src, []);
181
+ _graphOut.get(e.src).push(e);
182
+ if (!_graphIn.has(e.dst)) _graphIn.set(e.dst, []);
183
+ _graphIn.get(e.dst).push(e);
184
+ }
185
+ }
186
+ function getGraphNode(slug) {
187
+ loadGraph();
188
+ return _graphNodes.get(slug) ?? null;
189
+ }
190
+ function graphEdges(slug, opts = {}) {
191
+ loadGraph();
192
+ const { predicate, direction = "out" } = opts;
193
+ const collected = [];
194
+ if (direction === "out" || direction === "both") collected.push(..._graphOut.get(slug) ?? []);
195
+ if (direction === "in" || direction === "both") collected.push(..._graphIn.get(slug) ?? []);
196
+ return predicate ? collected.filter((e) => e.predicate === predicate) : collected;
197
+ }
198
+ function graphRelated(slug, opts = {}) {
199
+ loadGraph();
200
+ const { predicate, direction = "out", limit = 25 } = opts;
201
+ const seen = /* @__PURE__ */ new Set();
202
+ const out = [];
203
+ const push = (otherSlug) => {
204
+ if (seen.has(otherSlug)) return;
205
+ const n = _graphNodes.get(otherSlug);
206
+ if (n) {
207
+ seen.add(otherSlug);
208
+ out.push(n);
209
+ }
210
+ };
211
+ if (direction === "out" || direction === "both") {
212
+ for (const e of _graphOut.get(slug) ?? []) if (!predicate || e.predicate === predicate) push(e.dst);
213
+ }
214
+ if (direction === "in" || direction === "both") {
215
+ for (const e of _graphIn.get(slug) ?? []) if (!predicate || e.predicate === predicate) push(e.src);
216
+ }
217
+ return out.slice(0, limit);
218
+ }
219
+ function graphPath(a, b, maxDepth = 4) {
220
+ loadGraph();
221
+ if (!_graphNodes.has(a) || !_graphNodes.has(b)) return null;
222
+ if (a === b) return [a];
223
+ const visited = /* @__PURE__ */ new Set([a]);
224
+ let frontier = [[a]];
225
+ for (let depth = 0; depth < maxDepth; depth++) {
226
+ const next = [];
227
+ for (const pathSoFar of frontier) {
228
+ const last = pathSoFar[pathSoFar.length - 1];
229
+ const nbrs = /* @__PURE__ */ new Set();
230
+ for (const e of _graphOut.get(last) ?? []) nbrs.add(e.dst);
231
+ for (const e of _graphIn.get(last) ?? []) nbrs.add(e.src);
232
+ for (const nb of nbrs) {
233
+ if (visited.has(nb)) continue;
234
+ const newPath = [...pathSoFar, nb];
235
+ if (nb === b) return newPath;
236
+ visited.add(nb);
237
+ next.push(newPath);
238
+ }
239
+ }
240
+ frontier = next;
241
+ }
242
+ return null;
243
+ }
166
244
 
167
245
  // src/tools/ipl-leaderboard.ts
168
246
  var LEADERBOARD_ASPECTS = [
@@ -646,7 +724,7 @@ var TOOLS = [
646
724
  },
647
725
  {
648
726
  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).",
727
+ 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
728
  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
729
  },
652
730
  {
@@ -716,7 +794,7 @@ var TOOLS = [
716
794
  },
717
795
  {
718
796
  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.",
797
+ 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
798
  inputSchema: { type: "object", properties: { playerSlug: { type: "string", description: "Omit for leaderboard" }, limit: { type: "number", description: "Leaderboard rows (default 15)" } }, additionalProperties: false }
721
799
  },
722
800
  // ── GROUP 2: MLC ────────────────────────────────────────────────────
@@ -765,8 +843,25 @@ var TOOLS = [
765
843
  name: "get_ipl_leaderboard",
766
844
  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}.',
767
845
  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 }
846
+ },
847
+ // ── GROUP 5: Knowledge graph (L3) ───────────────────────────────────
848
+ {
849
+ name: "get_related_entities",
850
+ 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.',
851
+ 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 }
852
+ },
853
+ {
854
+ name: "get_player_connections",
855
+ 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.`,
856
+ 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 }
857
+ },
858
+ {
859
+ name: "get_graph_path",
860
+ 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".',
861
+ 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 }
768
862
  }
769
863
  ];
864
+ var GraphPredicates = ["plays_for", "faced", "dismissed_by"];
770
865
  var validators = {
771
866
  get_dataset_summary: z.object({}).strict(),
772
867
  search_players: z.object({ query: z.string(), limit: z.number().optional() }).strict(),
@@ -796,7 +891,10 @@ var validators = {
796
891
  get_mlc_match_claim: z.object({ matchId: z.string(), kind: z.enum(["top-batter", "top-bowler", "biggest-partnership", "pp-control", "death-domination"]) }).strict(),
797
892
  list_mlc_matches: z.object({ season: z.string().optional(), teamSlug: z.string().optional(), limit: z.number().optional() }).strict(),
798
893
  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()
894
+ get_ipl_leaderboard: z.object({ aspect: z.string(), season: z.string().optional(), limit: z.number().optional() }).strict(),
895
+ 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(),
896
+ get_player_connections: z.object({ playerSlug: z.string(), limit: z.number().optional() }).strict(),
897
+ get_graph_path: z.object({ fromSlug: z.string(), toSlug: z.string(), maxDepth: z.number().optional() }).strict()
800
898
  };
801
899
  function handleDatasetSummary() {
802
900
  const md = metadata();
@@ -1226,7 +1324,7 @@ function handleListMlcLeaderboards(args) {
1226
1324
  }
1227
1325
  const limit = Math.max(1, Math.min(100, args.limit ?? 20));
1228
1326
  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));
1327
+ 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
1328
  }
1231
1329
  var IPL_LB_SPECS = {
1232
1330
  // ── Batting aggregates ──────────────────────────────────────────────
@@ -1318,8 +1416,88 @@ function handleIplLeaderboard(args) {
1318
1416
  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
1417
  }, canonical);
1320
1418
  }
1419
+ function urlForGraphNode(n) {
1420
+ switch (n.type) {
1421
+ case "player":
1422
+ return `${SITE}/players/${n.slug}`;
1423
+ case "franchise":
1424
+ return `${SITE}/teams/${n.slug}`;
1425
+ case "venue":
1426
+ return `${SITE}/venues/${n.slug}`;
1427
+ default:
1428
+ return null;
1429
+ }
1430
+ }
1431
+ function projectGraphNode(n) {
1432
+ return { id: n.slug, type: n.type, name: n.name, canonical_url: urlForGraphNode(n) };
1433
+ }
1434
+ function handleGetRelatedEntities(args) {
1435
+ const slug = String(args.slug ?? "");
1436
+ const node = getGraphNode(slug);
1437
+ if (!node) return notFound(`No graph entity with slug "${slug}".`);
1438
+ const predicate = args.predicate ? String(args.predicate) : void 0;
1439
+ const direction = args.direction ?? "out";
1440
+ const limit = typeof args.limit === "number" ? Math.min(Math.max(args.limit, 1), 50) : 25;
1441
+ const related = graphRelated(slug, { predicate, direction, limit });
1442
+ return ok({
1443
+ entity: projectGraphNode(node),
1444
+ predicate: predicate ?? "all",
1445
+ direction,
1446
+ count: related.length,
1447
+ related: related.map(projectGraphNode),
1448
+ source: "CricketStudio knowledge graph (L3)"
1449
+ }, urlForGraphNode(node) ?? void 0);
1450
+ }
1451
+ function handleGetPlayerConnections(args) {
1452
+ const slug = String(args.playerSlug ?? "");
1453
+ const node = getGraphNode(slug);
1454
+ if (!node || node.type !== "player") return notFound(`No player in the graph with slug "${slug}".`);
1455
+ const limit = typeof args.limit === "number" ? Math.min(Math.max(args.limit, 1), 50) : 10;
1456
+ const playsFor = graphEdges(slug, { predicate: "plays_for", direction: "out" }).map((e) => getGraphNode(e.dst)).filter((n) => !!n).map(projectGraphNode);
1457
+ 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) => {
1458
+ const n = getGraphNode(e.dst);
1459
+ return n ? { ...projectGraphNode(n), deliveries: e.props?.deliveries ?? 0, runs: e.props?.runs ?? 0, dismissals: e.props?.dismissals ?? 0 } : null;
1460
+ }).filter((x) => !!x);
1461
+ 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) => {
1462
+ const n = getGraphNode(e.dst);
1463
+ return n ? { ...projectGraphNode(n), dismissals: e.props?.dismissals ?? 0 } : null;
1464
+ }).filter((x) => !!x);
1465
+ return ok({
1466
+ player: projectGraphNode(node),
1467
+ playsFor,
1468
+ mostFacedBowlers,
1469
+ dismissedByMost,
1470
+ window: "IPL career (faced/dismissed) + IPL 2026 (plays_for)",
1471
+ source: "CricketStudio knowledge graph (L3)",
1472
+ note: "Matchup edges mirror the get_player_h2h pair set; not every opponent is present."
1473
+ }, urlForGraphNode(node) ?? void 0);
1474
+ }
1475
+ function handleGetGraphPath(args) {
1476
+ const fromSlug = String(args.fromSlug ?? "");
1477
+ const toSlug = String(args.toSlug ?? "");
1478
+ const a = getGraphNode(fromSlug);
1479
+ if (!a) return notFound(`No graph entity with slug "${fromSlug}".`);
1480
+ const b = getGraphNode(toSlug);
1481
+ if (!b) return notFound(`No graph entity with slug "${toSlug}".`);
1482
+ const maxDepth = typeof args.maxDepth === "number" ? Math.min(Math.max(args.maxDepth, 1), 4) : 3;
1483
+ const ids = graphPath(fromSlug, toSlug, maxDepth);
1484
+ if (!ids) {
1485
+ return ok({ from: projectGraphNode(a), to: projectGraphNode(b), connected: false, path: [], note: `No connection within ${maxDepth} hops.`, source: "CricketStudio knowledge graph (L3)" });
1486
+ }
1487
+ return ok({
1488
+ from: projectGraphNode(a),
1489
+ to: projectGraphNode(b),
1490
+ connected: true,
1491
+ hops: ids.length - 1,
1492
+ path: ids.map((id) => {
1493
+ const n = getGraphNode(id);
1494
+ return n ? projectGraphNode(n) : { id, type: "unknown", name: null, canonical_url: null };
1495
+ }),
1496
+ source: "CricketStudio knowledge graph (L3)"
1497
+ });
1498
+ }
1321
1499
  var server = new Server(
1322
- { name: "cricketstudio", version: "1.0.0" },
1500
+ { name: "cricketstudio", version: "1.1.0" },
1323
1501
  { capabilities: { tools: {} } }
1324
1502
  );
1325
1503
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
@@ -1390,6 +1568,12 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1390
1568
  return handleListMlcLeaderboards(args);
1391
1569
  case "get_ipl_leaderboard":
1392
1570
  return handleIplLeaderboard(args);
1571
+ case "get_related_entities":
1572
+ return handleGetRelatedEntities(args);
1573
+ case "get_player_connections":
1574
+ return handleGetPlayerConnections(args);
1575
+ case "get_graph_path":
1576
+ return handleGetGraphPath(args);
1393
1577
  default:
1394
1578
  return ok({ error: "unknown_tool", tool: name });
1395
1579
  }
package/package.json CHANGED
@@ -1,55 +1,56 @@
1
- {
2
- "name": "cricketstudio-mcp",
3
- "version": "1.0.2",
4
- "description": "CricketStudio MCP server — 29 tools for IPL 2026, IPL historical (18 seasons), and Major League Cricket data. Citation-grade atomic claims with provenance.",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "bin": {
8
- "cricketstudio-mcp": "dist/index.js"
9
- },
10
- "scripts": {
11
- "build": "esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js --format=esm --packages=external",
12
- "dev": "tsx src/index.ts",
13
- "typecheck": "tsc --noEmit",
14
- "prepare": "npm run build",
15
- "snapshot": "node scripts/build-snapshot.mjs"
16
- },
17
- "keywords": [
18
- "mcp",
19
- "cricket",
20
- "ipl",
21
- "ipl-2026",
22
- "ipl-historical",
23
- "mlc",
24
- "cricketstudio",
25
- "claude",
26
- "model-context-protocol"
27
- ],
28
- "author": "Arul Anand / CricketStudio <hello@cricketstudio.ai>",
29
- "license": "MIT",
30
- "repository": {
31
- "type": "git",
32
- "url": "git+https://github.com/i-m-arul/cricketstudio-mcp.git"
33
- },
34
- "homepage": "https://players.cricketstudio.ai/mcp",
35
- "mcpName": "io.github.i-m-arul/cricketstudio-mcp",
36
- "engines": {
37
- "node": ">=18"
38
- },
39
- "peerDependencies": {},
40
- "dependencies": {
41
- "@modelcontextprotocol/sdk": "^1.0.0",
42
- "zod": "^3.0.0"
43
- },
44
- "devDependencies": {
45
- "typescript": "^5.0.0",
46
- "tsx": "^4.0.0",
47
- "esbuild": "^0.20.0"
48
- },
49
- "files": [
50
- "dist/",
51
- "data/snapshot/",
52
- "README.md",
53
- "LICENSE"
54
- ]
55
- }
1
+ {
2
+ "name": "cricketstudio-mcp",
3
+ "version": "1.2.0",
4
+ "description": "CricketStudio MCP server — 32 tools (incl. an L3 knowledge-graph layer) for IPL 2026, IPL historical (18 seasons), and Major League Cricket data. Citation-grade atomic claims with provenance.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "cricketstudio-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js --format=esm --packages=external",
12
+ "dev": "tsx src/index.ts",
13
+ "typecheck": "tsc --noEmit",
14
+ "prepare": "npm run build",
15
+ "smoke": "node scripts/smoke-test.mjs",
16
+ "validate": "node scripts/validate-snapshot.mjs"
17
+ },
18
+ "keywords": [
19
+ "mcp",
20
+ "cricket",
21
+ "ipl",
22
+ "ipl-2026",
23
+ "ipl-historical",
24
+ "mlc",
25
+ "cricketstudio",
26
+ "claude",
27
+ "model-context-protocol"
28
+ ],
29
+ "author": "Arul Anand / CricketStudio <hello@cricketstudio.ai>",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/i-m-arul/cricketstudio-mcp.git"
34
+ },
35
+ "homepage": "https://players.cricketstudio.ai/mcp",
36
+ "mcpName": "io.github.i-m-arul/cricketstudio-mcp",
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.0.0",
42
+ "zod": "^3.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^22.19.21",
46
+ "esbuild": "^0.28.0",
47
+ "tsx": "^4.0.0",
48
+ "typescript": "^5.0.0"
49
+ },
50
+ "files": [
51
+ "dist/",
52
+ "data/snapshot/",
53
+ "README.md",
54
+ "LICENSE"
55
+ ]
56
+ }