@pipeworx/mcp-cricsheet 0.1.0 → 0.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.
package/tools.json ADDED
@@ -0,0 +1,182 @@
1
+ [
2
+ {
3
+ "name": "cricsheet_match",
4
+ "description": "Full result and scorecard for one cricket match: teams, venue, date, toss, result, and for every innings the batting card (each batter's runs/balls/4s/6s/how out) and bowling card (each bowler's overs/runs/wickets). Covers Test, ODI and T20 International cricket plus the IPL (Cricsheet, ODC-BY). Use cricsheet_find_matches first to get a match_id if you don't already have one.",
5
+ "inputSchema": {
6
+ "type": "object",
7
+ "properties": {
8
+ "match_id": {
9
+ "type": "number",
10
+ "description": "Cricsheet match id, e.g. 1082591. Get one from cricsheet_find_matches."
11
+ }
12
+ },
13
+ "required": [
14
+ "match_id"
15
+ ]
16
+ }
17
+ },
18
+ {
19
+ "name": "cricsheet_player_stats",
20
+ "description": "A cricketer's career batting and bowling aggregates (matches, runs, average, strike rate, 100s/50s; wickets, bowling average, economy) from Cricsheet's Test/ODI/T20I/IPL match data. Pass a name or partial name (\"Kohli\", \"V Kohli\") -- Cricsheet stores short-form names, so this matches case-insensitively and returns a candidate list if the name is ambiguous. Optionally scope to one format with `format`. Covers only matches Cricsheet has scored ball-by-ball, not a player's entire career.",
21
+ "inputSchema": {
22
+ "type": "object",
23
+ "properties": {
24
+ "player": {
25
+ "type": "string",
26
+ "description": "Player name or partial name, e.g. \"Kohli\" or \"V Kohli\"."
27
+ },
28
+ "format": {
29
+ "type": "string",
30
+ "description": "Match format: \"Test\", \"ODI\", \"T20I\" (international T20), or \"IPL\". Omit to search across every format this pack holds."
31
+ }
32
+ },
33
+ "required": [
34
+ "player"
35
+ ]
36
+ }
37
+ },
38
+ {
39
+ "name": "cricsheet_head_to_head",
40
+ "description": "Head-to-head record between two cricket teams from Cricsheet's Test/ODI/T20I/IPL match data: total matches, each team's wins, ties/no-results, and the most recent matches with their results. Pass team names as Cricsheet uses them, e.g. \"India\", \"Australia\", \"Mumbai Indians\" (IPL). Optionally scope to one format with `format`.",
41
+ "inputSchema": {
42
+ "type": "object",
43
+ "properties": {
44
+ "team_a": {
45
+ "type": "string",
46
+ "description": "First team name, e.g. \"India\"."
47
+ },
48
+ "team_b": {
49
+ "type": "string",
50
+ "description": "Second team name, e.g. \"Australia\"."
51
+ },
52
+ "format": {
53
+ "type": "string",
54
+ "description": "Match format: \"Test\", \"ODI\", \"T20I\" (international T20), or \"IPL\". Omit to search across every format this pack holds."
55
+ },
56
+ "limit": {
57
+ "type": "number",
58
+ "description": "Max recent matches to list, 1-100. Default 10."
59
+ },
60
+ "offset": {
61
+ "type": "number",
62
+ "description": "Pagination offset into the recent-matches list. Default 0."
63
+ }
64
+ },
65
+ "required": [
66
+ "team_a",
67
+ "team_b"
68
+ ]
69
+ }
70
+ },
71
+ {
72
+ "name": "cricsheet_find_matches",
73
+ "description": "Search Cricsheet's Test/ODI/T20I/IPL match index by team, format, season, venue and/or player, returning match_id plus date/teams/venue/result for each hit -- use this to discover match_ids for cricsheet_match. With no filters, returns the most recent matches across every format. Provide at least one filter to narrow a specific search.",
74
+ "inputSchema": {
75
+ "type": "object",
76
+ "properties": {
77
+ "team": {
78
+ "type": "string",
79
+ "description": "Team name, e.g. \"England\" or \"Chennai Super Kings\" (IPL)."
80
+ },
81
+ "format": {
82
+ "type": "string",
83
+ "description": "Match format: \"Test\", \"ODI\", \"T20I\" (international T20), or \"IPL\". Omit to search across every format this pack holds."
84
+ },
85
+ "season": {
86
+ "type": "string",
87
+ "description": "Season label as Cricsheet records it, e.g. \"2023\" or \"2007/08\"."
88
+ },
89
+ "venue": {
90
+ "type": "string",
91
+ "description": "Venue name or partial venue name, e.g. \"Lord's\"."
92
+ },
93
+ "player": {
94
+ "type": "string",
95
+ "description": "Exact player name as Cricsheet stores it (see cricsheet_player_stats to resolve a partial name first)."
96
+ },
97
+ "limit": {
98
+ "type": "number",
99
+ "description": "Max matches to return, 1-200. Default 50."
100
+ },
101
+ "offset": {
102
+ "type": "number",
103
+ "description": "Pagination offset. Default 0."
104
+ }
105
+ }
106
+ }
107
+ },
108
+ {
109
+ "name": "cricsheet_chase_search",
110
+ "description": "Compound cricket match search on the second innings (the chase): filter limited-overs matches (ODI, T20I, IPL) by whether the side batting second won or lost, wickets remaining and balls remaining at the end of the chase, date range, season, tournament/event, team, gender and format. Each hit returns target, chase score, wickets lost, balls used, wickets_remaining (10 minus wickets lost) and balls_remaining (scheduled overs minus legal balls bowled; null for rain-affected matches decided by a method such as DLS). Example: T20I chases won with 5+ wickets and 10+ balls to spare since 2024. Source: Cricsheet match data (ODC-BY). Tests are excluded.",
111
+ "inputSchema": {
112
+ "type": "object",
113
+ "properties": {
114
+ "second_innings_result": {
115
+ "type": "string",
116
+ "enum": [
117
+ "won",
118
+ "lost",
119
+ "any"
120
+ ],
121
+ "description": "Did the side batting second win? Default \"won\" (a successful chase)."
122
+ },
123
+ "format": {
124
+ "type": "string",
125
+ "description": "Match format: \"ODI\", \"T20I\" or \"IPL\". Omit for all three."
126
+ },
127
+ "min_wickets_remaining": {
128
+ "type": "number",
129
+ "description": "Chasing side had at least this many wickets in hand at the end (0-10)."
130
+ },
131
+ "max_wickets_remaining": {
132
+ "type": "number",
133
+ "description": "Chasing side had at most this many wickets in hand at the end (0-10)."
134
+ },
135
+ "min_balls_remaining": {
136
+ "type": "number",
137
+ "description": "At least this many legal balls unused at the end of the chase (assumes full scheduled overs; 120 in T20, 300 in ODI)."
138
+ },
139
+ "max_balls_remaining": {
140
+ "type": "number",
141
+ "description": "At most this many legal balls unused (0 = chase went to the last ball)."
142
+ },
143
+ "team": {
144
+ "type": "string",
145
+ "description": "Chasing team name as Cricsheet uses it, e.g. \"India\"."
146
+ },
147
+ "gender": {
148
+ "type": "string",
149
+ "enum": [
150
+ "male",
151
+ "female"
152
+ ],
153
+ "description": "Men's or women's cricket."
154
+ },
155
+ "season": {
156
+ "type": "string",
157
+ "description": "Season label, e.g. \"2024\" or \"2023/24\"."
158
+ },
159
+ "event": {
160
+ "type": "string",
161
+ "description": "Tournament/event name or fragment, e.g. \"World Cup\" or \"Indian Premier League\"."
162
+ },
163
+ "date_from": {
164
+ "type": "string",
165
+ "description": "Earliest match date, YYYY-MM-DD."
166
+ },
167
+ "date_to": {
168
+ "type": "string",
169
+ "description": "Latest match date, YYYY-MM-DD."
170
+ },
171
+ "limit": {
172
+ "type": "number",
173
+ "description": "Max matches to return, 1-200. Default 25."
174
+ },
175
+ "offset": {
176
+ "type": "number",
177
+ "description": "Pagination offset. Default 0."
178
+ }
179
+ }
180
+ }
181
+ }
182
+ ]
package/src/index.ts DELETED
@@ -1,423 +0,0 @@
1
- interface McpToolDefinition {
2
- name: string;
3
- description: string;
4
- /** Human-facing one-liner (fleet #1967). Optional; consumers fall back to
5
- * description. Kept in step with shared/src/types.ts — scripts/lib/
6
- * check-inlined-types.mjs reports drift at publish time. */
7
- summary?: string;
8
- inputSchema: {
9
- type: 'object';
10
- properties: Record<string, unknown>;
11
- required?: string[];
12
- anyOf?: Array<{ required: string[] }>;
13
- oneOf?: Array<{ required: string[] }>;
14
- allOf?: Array<{ required: string[] }>;
15
- };
16
- outputSchema?: Record<string, unknown>;
17
- }
18
-
19
- interface McpToolExport {
20
- tools: McpToolDefinition[];
21
- callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
22
- meter?: { credits: number };
23
- cost?: Record<string, unknown>;
24
- provider?: string;
25
- }
26
-
27
- /**
28
- * cricsheet -- ball-by-ball cricket data collapsed to match / player / team
29
- * questions (fleet #2975).
30
- *
31
- * Data: Cricsheet (cricsheet.org) -- per-match JSON files for Test, One Day
32
- * International and Twenty20 International cricket plus the Indian Premier
33
- * League (ODC Attribution License, ODC-BY; attribution: Cricsheet). Cricsheet
34
- * publishes bulk files only, so this pack builds a copy
35
- * (scripts/cricsheet/load-data.mjs) into a D1 database the gateway hands it
36
- * as `_d1` (pack-manifest "d1Binding": "CRICSHEET_DB"). Design note: this
37
- * file + store.ts.
38
- *
39
- * Every match file is parsed ball-by-ball IN THE LOADER and collapsed to
40
- * match, innings, and batting/bowling scorecard rows -- individual
41
- * deliveries are not stored or served (CLAUDE.md "prefer the smallest store
42
- * that answers the tool questions; do not store every ball if aggregates
43
- * answer the questions").
44
- *
45
- * Every response carries `source`, `source_url`, `license` and `data_as_of`.
46
- */
47
- import {
48
- battingAggregate,
49
- bowlingAggregate,
50
- dataAsOf,
51
- findMatches,
52
- findMatchesCount,
53
- getBattingCards,
54
- getBowlingCards,
55
- getInnings,
56
- getMatch,
57
- headToHeadCount,
58
- headToHeadMatches,
59
- requireDb,
60
- resolveFormat,
61
- resolvePlayer,
62
- type D1Like,
63
- type Format,
64
- } from './store.js';
65
-
66
- const SOURCE = 'Cricsheet, ball-by-ball cricket match data';
67
- const SOURCE_URL = 'https://cricsheet.org';
68
- const LICENSE = 'ODC-BY (Open Data Commons Attribution License); attribution: Cricsheet';
69
-
70
- const FORMAT_DESC = 'Match format: "Test", "ODI", "T20I" (international T20), or "IPL". Omit to search across every format this pack holds.';
71
-
72
- const tools: McpToolExport['tools'] = [
73
- {
74
- name: 'cricsheet_match',
75
- description:
76
- 'Full result and scorecard for one cricket match: teams, venue, date, toss, result, and for every innings the batting card (each batter\'s runs/balls/4s/6s/how out) and bowling card (each bowler\'s overs/runs/wickets). Covers Test, ODI and T20 International cricket plus the IPL (Cricsheet, ODC-BY). Use cricsheet_find_matches first to get a match_id if you don\'t already have one.',
77
- summary: "One match's full result and batting/bowling scorecards by match_id.",
78
- inputSchema: {
79
- type: 'object' as const,
80
- properties: {
81
- match_id: { type: 'number', description: 'Cricsheet match id, e.g. 1082591. Get one from cricsheet_find_matches.' },
82
- },
83
- required: ['match_id'],
84
- },
85
- },
86
- {
87
- name: 'cricsheet_player_stats',
88
- description:
89
- 'A cricketer\'s career batting and bowling aggregates (matches, runs, average, strike rate, 100s/50s; wickets, bowling average, economy) from Cricsheet\'s Test/ODI/T20I/IPL match data. Pass a name or partial name ("Kohli", "V Kohli") -- Cricsheet stores short-form names, so this matches case-insensitively and returns a candidate list if the name is ambiguous. Optionally scope to one format with `format`. Covers only matches Cricsheet has scored ball-by-ball, not a player\'s entire career.',
90
- summary: "A cricketer's batting/bowling career aggregates, optionally scoped to one format.",
91
- inputSchema: {
92
- type: 'object' as const,
93
- properties: {
94
- player: { type: 'string', description: 'Player name or partial name, e.g. "Kohli" or "V Kohli".' },
95
- format: { type: 'string', description: FORMAT_DESC },
96
- },
97
- required: ['player'],
98
- },
99
- },
100
- {
101
- name: 'cricsheet_head_to_head',
102
- description:
103
- 'Head-to-head record between two cricket teams from Cricsheet\'s Test/ODI/T20I/IPL match data: total matches, each team\'s wins, ties/no-results, and the most recent matches with their results. Pass team names as Cricsheet uses them, e.g. "India", "Australia", "Mumbai Indians" (IPL). Optionally scope to one format with `format`.',
104
- summary: 'Win/loss head-to-head record between two teams, optionally scoped to one format.',
105
- inputSchema: {
106
- type: 'object' as const,
107
- properties: {
108
- team_a: { type: 'string', description: 'First team name, e.g. "India".' },
109
- team_b: { type: 'string', description: 'Second team name, e.g. "Australia".' },
110
- format: { type: 'string', description: FORMAT_DESC },
111
- limit: { type: 'number', description: 'Max recent matches to list, 1-100. Default 10.' },
112
- offset: { type: 'number', description: 'Pagination offset into the recent-matches list. Default 0.' },
113
- },
114
- required: ['team_a', 'team_b'],
115
- },
116
- },
117
- {
118
- name: 'cricsheet_find_matches',
119
- description:
120
- 'Search Cricsheet\'s Test/ODI/T20I/IPL match index by team, format, season, venue and/or player, returning match_id plus date/teams/venue/result for each hit -- use this to discover match_ids for cricsheet_match. With no filters, returns the most recent matches across every format. Provide at least one filter to narrow a specific search.',
121
- summary: 'Search matches by team/format/season/venue/player to get match_ids.',
122
- inputSchema: {
123
- type: 'object' as const,
124
- properties: {
125
- team: { type: 'string', description: 'Team name, e.g. "England" or "Chennai Super Kings" (IPL).' },
126
- format: { type: 'string', description: FORMAT_DESC },
127
- season: { type: 'string', description: 'Season label as Cricsheet records it, e.g. "2023" or "2007/08".' },
128
- venue: { type: 'string', description: 'Venue name or partial venue name, e.g. "Lord\'s".' },
129
- player: { type: 'string', description: 'Exact player name as Cricsheet stores it (see cricsheet_player_stats to resolve a partial name first).' },
130
- limit: { type: 'number', description: 'Max matches to return, 1-200. Default 50.' },
131
- offset: { type: 'number', description: 'Pagination offset. Default 0.' },
132
- },
133
- },
134
- },
135
- ];
136
-
137
- function clampInt(v: unknown, lo: number, hi: number, d: number): number {
138
- const n = Math.floor(Number(v));
139
- return Number.isFinite(n) ? Math.min(hi, Math.max(lo, n)) : d;
140
- }
141
- function str(v: unknown): string | null {
142
- return v != null && v !== '' ? String(v).trim() : null;
143
- }
144
-
145
- function serializeMatch(m: {
146
- match_id: number;
147
- format: string;
148
- gender: string;
149
- team1: string;
150
- team2: string;
151
- match_date: string;
152
- season: string | null;
153
- venue: string | null;
154
- city: string | null;
155
- event_name: string | null;
156
- toss_winner: string | null;
157
- toss_decision: string | null;
158
- outcome_winner: string | null;
159
- outcome_by_runs: number | null;
160
- outcome_by_wickets: number | null;
161
- outcome_by_innings: number | null;
162
- outcome_method: string | null;
163
- result: string | null;
164
- player_of_match: string | null;
165
- }) {
166
- return {
167
- match_id: m.match_id,
168
- format: m.format,
169
- gender: m.gender,
170
- team1: m.team1,
171
- team2: m.team2,
172
- match_date: m.match_date,
173
- season: m.season,
174
- venue: m.venue,
175
- city: m.city,
176
- event: m.event_name,
177
- toss: m.toss_winner ? { winner: m.toss_winner, decision: m.toss_decision } : null,
178
- outcome: {
179
- winner: m.outcome_winner,
180
- by_runs: m.outcome_by_runs,
181
- by_wickets: m.outcome_by_wickets,
182
- by_innings: m.outcome_by_innings === 1,
183
- method: m.outcome_method,
184
- result: m.result,
185
- },
186
- player_of_match: m.player_of_match,
187
- };
188
- }
189
-
190
- async function cricsheetMatchTool(db: D1Like, args: Record<string, unknown>) {
191
- const asOf = await dataAsOf(db);
192
- const matchId = Math.floor(Number(args.match_id));
193
- if (!Number.isFinite(matchId)) {
194
- return { found: false, reason: 'invalid_match_id', message: 'match_id must be a number.', data_as_of: asOf };
195
- }
196
- const match = await getMatch(db, matchId);
197
- if (!match) {
198
- return {
199
- found: false,
200
- reason: 'not_found',
201
- match_id: matchId,
202
- message: `No match held with match_id=${matchId}.`,
203
- hint: 'Call cricsheet_find_matches to search by team/format/season/venue and get a valid match_id.',
204
- data_as_of: asOf,
205
- };
206
- }
207
- const [innings, batting, bowling] = await Promise.all([getInnings(db, matchId), getBattingCards(db, matchId), getBowlingCards(db, matchId)]);
208
- const inningsOut = innings.map((inn) => ({
209
- innings: inn.innings_num,
210
- batting_team: inn.batting_team,
211
- total_runs: inn.total_runs,
212
- total_wickets: inn.total_wickets,
213
- overs: `${Math.floor(inn.legal_balls / 6)}.${inn.legal_balls % 6}`,
214
- batting_card: batting
215
- .filter((b) => b.innings_num === inn.innings_num)
216
- .map((b) => ({
217
- position: b.position,
218
- player: b.player,
219
- runs: b.runs,
220
- balls: b.balls,
221
- fours: b.fours,
222
- sixes: b.sixes,
223
- not_out: b.not_out === 1,
224
- dismissal: b.not_out === 1 ? 'not out' : b.dismissal_kind ? `${b.dismissal_kind}${b.dismissal_bowler ? ` b ${b.dismissal_bowler}` : ''}` : null,
225
- })),
226
- bowling_card: bowling
227
- .filter((b) => b.innings_num === inn.innings_num)
228
- .map((b) => ({
229
- order: b.order_num,
230
- player: b.player,
231
- overs: `${Math.floor(b.balls / 6)}.${b.balls % 6}`,
232
- runs_conceded: b.runs_conceded,
233
- wickets: b.wickets,
234
- economy: b.balls > 0 ? Math.round((b.runs_conceded / (b.balls / 6)) * 100) / 100 : null,
235
- })),
236
- }));
237
- return {
238
- found: true,
239
- ...serializeMatch(match),
240
- innings: inningsOut,
241
- source: SOURCE,
242
- source_url: SOURCE_URL,
243
- license: LICENSE,
244
- data_as_of: asOf,
245
- };
246
- }
247
-
248
- async function cricsheetPlayerStatsTool(db: D1Like, args: Record<string, unknown>) {
249
- const asOf = await dataAsOf(db);
250
- const query = str(args.player);
251
- if (!query) {
252
- return { found: false, reason: 'missing_player', message: 'player is required.', data_as_of: asOf };
253
- }
254
- let format: Format | null = null;
255
- if (args.format != null && args.format !== '') {
256
- format = resolveFormat(args.format);
257
- if (!format) {
258
- return { found: false, reason: 'unknown_format', message: `${JSON.stringify(args.format)} is not a recognized format. Use "Test", "ODI", "T20I", or "IPL".`, data_as_of: asOf };
259
- }
260
- }
261
- const { resolved, candidates } = await resolvePlayer(db, query);
262
- if (!resolved) {
263
- if (!candidates.length) {
264
- return {
265
- found: false,
266
- reason: 'no_match',
267
- message: `No player matching ${JSON.stringify(query)} held in this pack.`,
268
- hint: 'Cricsheet uses short-form names (e.g. "V Kohli", "SR Watson") -- try a surname or a shorter fragment.',
269
- data_as_of: asOf,
270
- };
271
- }
272
- return {
273
- found: false,
274
- reason: 'ambiguous',
275
- message: `${JSON.stringify(query)} matches ${candidates.length} players.`,
276
- candidates: candidates.map((c) => ({ player: c.player, matches: c.matches })),
277
- hint: 'Call again with the exact player string from candidates.',
278
- data_as_of: asOf,
279
- };
280
- }
281
- const [batting, bowling] = await Promise.all([battingAggregate(db, resolved, format), bowlingAggregate(db, resolved, format)]);
282
- if (batting.innings === 0 && bowling.innings === 0) {
283
- return {
284
- found: false,
285
- reason: 'no_data_for_format',
286
- player: resolved,
287
- format: format ?? 'all',
288
- message: `${resolved} has no ${format ?? 'Cricsheet'} innings held in this pack.`,
289
- hint: format ? 'Try again without format to search across every format.' : undefined,
290
- data_as_of: asOf,
291
- };
292
- }
293
- return {
294
- found: true,
295
- player: resolved,
296
- format: format ?? 'all',
297
- batting,
298
- bowling,
299
- source: SOURCE,
300
- source_url: SOURCE_URL,
301
- license: LICENSE,
302
- data_as_of: asOf,
303
- };
304
- }
305
-
306
- async function cricsheetHeadToHeadTool(db: D1Like, args: Record<string, unknown>) {
307
- const asOf = await dataAsOf(db);
308
- const teamA = str(args.team_a);
309
- const teamB = str(args.team_b);
310
- if (!teamA || !teamB) {
311
- return { found: false, reason: 'missing_team', message: 'team_a and team_b are both required.', data_as_of: asOf };
312
- }
313
- let format: Format | null = null;
314
- if (args.format != null && args.format !== '') {
315
- format = resolveFormat(args.format);
316
- if (!format) {
317
- return { found: false, reason: 'unknown_format', message: `${JSON.stringify(args.format)} is not a recognized format. Use "Test", "ODI", "T20I", or "IPL".`, data_as_of: asOf };
318
- }
319
- }
320
- const limit = clampInt(args.limit, 1, 100, 10);
321
- const offset = Math.max(0, Math.floor(Number(args.offset) || 0));
322
- const filter = { team_a: teamA, team_b: teamB, format, limit, offset };
323
- const [rows, total] = await Promise.all([headToHeadMatches(db, filter), headToHeadCount(db, filter)]);
324
- if (!total) {
325
- return {
326
- found: false,
327
- reason: 'no_match',
328
- team_a: teamA,
329
- team_b: teamB,
330
- format: format ?? 'all',
331
- message: `No matches held between ${teamA} and ${teamB}${format ? ` in ${format}` : ''}.`,
332
- hint: 'Check team spelling (Cricsheet uses full names, e.g. "India" not "IND") or call cricsheet_find_matches with team= to see what is held.',
333
- data_as_of: asOf,
334
- };
335
- }
336
- let winsA = 0, winsB = 0, ties = 0, noResult = 0;
337
- for (const m of rows) {
338
- if (m.result === 'tie') ties++;
339
- else if (m.result) noResult++;
340
- else if (m.outcome_winner && m.outcome_winner.toLowerCase() === teamA.toLowerCase()) winsA++;
341
- else if (m.outcome_winner && m.outcome_winner.toLowerCase() === teamB.toLowerCase()) winsB++;
342
- }
343
- const truncated = rows.length < total;
344
- return {
345
- found: true,
346
- team_a: teamA,
347
- team_b: teamB,
348
- format: format ?? 'all',
349
- total_matching: total,
350
- returned: rows.length,
351
- truncated,
352
- ...(truncated ? { hint: 'More matches exist than were returned (recent-matches list only). Page with limit/offset.' } : {}),
353
- recent_matches_summary: { wins_in_returned_set: { [teamA]: winsA, [teamB]: winsB }, ties, no_result_or_draw: noResult },
354
- recent_matches: rows.map(serializeMatch),
355
- source: SOURCE,
356
- source_url: SOURCE_URL,
357
- license: LICENSE,
358
- data_as_of: asOf,
359
- };
360
- }
361
-
362
- async function cricsheetFindMatchesTool(db: D1Like, args: Record<string, unknown>) {
363
- const asOf = await dataAsOf(db);
364
- let format: Format | null = null;
365
- if (args.format != null && args.format !== '') {
366
- format = resolveFormat(args.format);
367
- if (!format) {
368
- return { found: false, reason: 'unknown_format', message: `${JSON.stringify(args.format)} is not a recognized format. Use "Test", "ODI", "T20I", or "IPL".`, data_as_of: asOf };
369
- }
370
- }
371
- const limit = clampInt(args.limit, 1, 200, 50);
372
- const offset = Math.max(0, Math.floor(Number(args.offset) || 0));
373
- const filter = {
374
- team: str(args.team) ?? undefined,
375
- format,
376
- season: str(args.season) ?? undefined,
377
- venue: str(args.venue) ?? undefined,
378
- player: str(args.player) ?? undefined,
379
- limit,
380
- offset,
381
- };
382
- const [rows, total] = await Promise.all([findMatches(db, filter), findMatchesCount(db, filter)]);
383
- if (!total) {
384
- return {
385
- found: false,
386
- reason: 'no_match',
387
- message: 'No matches held matching those filters.',
388
- hint: 'Loosen a filter -- venue is substring-matched, but team/season/player require the exact string Cricsheet uses.',
389
- data_as_of: asOf,
390
- };
391
- }
392
- const truncated = rows.length < total;
393
- return {
394
- found: true,
395
- total_matching: total,
396
- returned: rows.length,
397
- truncated,
398
- ...(truncated ? { hint: 'More matches exist than were returned. Narrow with team/format/season/venue/player, or page with limit/offset.' } : {}),
399
- matches: rows.map(serializeMatch),
400
- source: SOURCE,
401
- source_url: SOURCE_URL,
402
- license: LICENSE,
403
- data_as_of: asOf,
404
- };
405
- }
406
-
407
- async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
408
- const bound = requireDb(args._d1);
409
- switch (name) {
410
- case 'cricsheet_match':
411
- return cricsheetMatchTool(bound, args);
412
- case 'cricsheet_player_stats':
413
- return cricsheetPlayerStatsTool(bound, args);
414
- case 'cricsheet_head_to_head':
415
- return cricsheetHeadToHeadTool(bound, args);
416
- case 'cricsheet_find_matches':
417
- return cricsheetFindMatchesTool(bound, args);
418
- default:
419
- throw new Error(`Unknown tool: ${name}`);
420
- }
421
- }
422
-
423
- export default { tools, callTool, meter: { credits: 1 } } satisfies McpToolExport;
package/src/server.ts DELETED
@@ -1,48 +0,0 @@
1
- /**
2
- * Stdio MCP server entry point for @pipeworx/mcp-cricsheet.
3
- * Generated by scripts/publish-pack.sh — do not hand-edit in the pack repo;
4
- * edit scripts/publish-pack.sh (the server.ts heredoc) and republish instead.
5
- */
6
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
- import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
9
- import pack from './index.js';
10
-
11
-
12
- const server = new Server(
13
- { name: '@pipeworx/mcp-cricsheet', version: '0.1.0' },
14
- { capabilities: { tools: {} } },
15
- );
16
-
17
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
18
- tools: pack.tools.map((t) => ({
19
- name: t.name,
20
- description: t.description,
21
- inputSchema: t.inputSchema,
22
- })),
23
- }));
24
-
25
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
26
- const { name, arguments: args } = request.params;
27
- try {
28
- const callArgs = { ...((args ?? {}) as Record<string, unknown>) };
29
-
30
- const result = await pack.callTool(name, callArgs);
31
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
32
- } catch (err) {
33
- return {
34
- content: [{ type: 'text', text: err instanceof Error ? err.message : String(err) }],
35
- isError: true,
36
- };
37
- }
38
- });
39
-
40
- async function main() {
41
- const transport = new StdioServerTransport();
42
- await server.connect(transport);
43
- }
44
-
45
- main().catch((err) => {
46
- console.error('Fatal error running server:', err);
47
- process.exit(1);
48
- });