@gotza02/sequential-thinking 10000.2.0 → 10000.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md.bak +197 -0
- package/dist/.gemini_graph_cache.json.bak +1641 -0
- package/dist/graph.d.ts +7 -0
- package/dist/graph.js +136 -113
- package/dist/intelligent-code.d.ts +8 -0
- package/dist/intelligent-code.js +152 -125
- package/dist/intelligent-code.test.d.ts +1 -0
- package/dist/intelligent-code.test.js +104 -0
- package/dist/lib/formatters.d.ts +9 -0
- package/dist/lib/formatters.js +119 -0
- package/dist/lib/validators.d.ts +45 -0
- package/dist/lib/validators.js +232 -0
- package/dist/lib.js +23 -265
- package/dist/tools/sports/core/base.d.ts +3 -2
- package/dist/tools/sports/core/base.js +12 -10
- package/dist/tools/sports/core/cache.d.ts +9 -0
- package/dist/tools/sports/core/cache.js +25 -3
- package/dist/tools/sports/core/types.d.ts +6 -2
- package/dist/tools/sports/providers/api.d.ts +4 -0
- package/dist/tools/sports/providers/api.js +110 -27
- package/dist/tools/sports/tools/betting.js +16 -16
- package/dist/tools/sports/tools/league.d.ts +2 -7
- package/dist/tools/sports/tools/league.js +198 -8
- package/dist/tools/sports/tools/live.js +80 -38
- package/dist/tools/sports/tools/match-calculations.d.ts +51 -0
- package/dist/tools/sports/tools/match-calculations.js +171 -0
- package/dist/tools/sports/tools/match-helpers.d.ts +21 -0
- package/dist/tools/sports/tools/match-helpers.js +57 -0
- package/dist/tools/sports/tools/match.js +227 -125
- package/dist/tools/sports.js +3 -3
- package/dist/utils.d.ts +111 -44
- package/dist/utils.js +510 -305
- package/dist/utils.test.js +3 -3
- package/package.json +1 -1
- package/CLAUDE.md +0 -231
|
@@ -65,7 +65,8 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
65
65
|
return response;
|
|
66
66
|
}
|
|
67
67
|
async getPlayer(playerId) {
|
|
68
|
-
const
|
|
68
|
+
const currentYear = new Date().getFullYear();
|
|
69
|
+
const response = await this.callAPI(`/players?id=${playerId}&season=${currentYear}`);
|
|
69
70
|
if (response.success && response.data?.response?.[0]) {
|
|
70
71
|
const player = this.transformPlayer(response.data.response[0]);
|
|
71
72
|
return { success: true, data: player, provider: 'api-football' };
|
|
@@ -81,7 +82,8 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
81
82
|
return response;
|
|
82
83
|
}
|
|
83
84
|
async searchPlayers(query) {
|
|
84
|
-
const
|
|
85
|
+
const currentYear = new Date().getFullYear();
|
|
86
|
+
const response = await this.callAPI(`/players?search=${encodeURIComponent(query)}&season=${currentYear}`);
|
|
85
87
|
if (response.success && response.data?.response) {
|
|
86
88
|
const players = response.data.response.map((p) => this.transformPlayer(p));
|
|
87
89
|
return { success: true, data: players, provider: 'api-football' };
|
|
@@ -156,11 +158,11 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
156
158
|
status: this.mapStatus(data.fixture.status.long),
|
|
157
159
|
venue: data.fixture.venue?.name,
|
|
158
160
|
score: data.goals ? {
|
|
159
|
-
home: data.goals.home,
|
|
160
|
-
away: data.goals.away,
|
|
161
|
-
halfTime: data.score
|
|
162
|
-
home: data.score.halftime.home,
|
|
163
|
-
away: data.score.halftime.away,
|
|
161
|
+
home: data.goals.home ?? 0,
|
|
162
|
+
away: data.goals.away ?? 0,
|
|
163
|
+
halfTime: data.score?.halftime ? {
|
|
164
|
+
home: data.score.halftime.home ?? 0,
|
|
165
|
+
away: data.score.halftime.away ?? 0,
|
|
164
166
|
} : undefined,
|
|
165
167
|
} : undefined,
|
|
166
168
|
minute: data.fixture.status?.elapsed,
|
|
@@ -208,7 +210,7 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
208
210
|
drawn: data.home.draw,
|
|
209
211
|
lost: data.home.lose,
|
|
210
212
|
goalsFor: data.home.goals.for,
|
|
211
|
-
|
|
213
|
+
goalsAgainst: data.home.goals.against,
|
|
212
214
|
} : undefined,
|
|
213
215
|
awayRecord: data.away ? {
|
|
214
216
|
played: data.away.played,
|
|
@@ -216,7 +218,7 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
216
218
|
drawn: data.away.draw,
|
|
217
219
|
lost: data.away.lose,
|
|
218
220
|
goalsFor: data.away.goals.for,
|
|
219
|
-
|
|
221
|
+
goalsAgainst: data.away.goals.against,
|
|
220
222
|
} : undefined,
|
|
221
223
|
};
|
|
222
224
|
}
|
|
@@ -230,6 +232,8 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
230
232
|
return 'postponed';
|
|
231
233
|
if (s.includes('cancelled'))
|
|
232
234
|
return 'cancelled';
|
|
235
|
+
if (s.includes('abandoned'))
|
|
236
|
+
return 'abandoned';
|
|
233
237
|
return 'scheduled';
|
|
234
238
|
}
|
|
235
239
|
mapPosition(pos) {
|
|
@@ -276,9 +280,17 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
276
280
|
homeGoals += isHomeTeam ? match.score.home : match.score.away;
|
|
277
281
|
awayGoals += isHomeTeam ? match.score.away : match.score.home;
|
|
278
282
|
}
|
|
283
|
+
// Determine teams - use first match data if available
|
|
284
|
+
const firstMatch = matches[0];
|
|
285
|
+
const homeTeamObj = firstMatch
|
|
286
|
+
? (firstMatch.homeTeam.id === homeId ? firstMatch.homeTeam : firstMatch.awayTeam)
|
|
287
|
+
: { id: homeId, name: 'Unknown', country: '' };
|
|
288
|
+
const awayTeamObj = firstMatch
|
|
289
|
+
? (firstMatch.homeTeam.id === awayId ? firstMatch.homeTeam : firstMatch.awayTeam)
|
|
290
|
+
: { id: awayId, name: 'Unknown', country: '' };
|
|
279
291
|
return {
|
|
280
|
-
homeTeam:
|
|
281
|
-
awayTeam:
|
|
292
|
+
homeTeam: homeTeamObj,
|
|
293
|
+
awayTeam: awayTeamObj,
|
|
282
294
|
totalMatches: matches.length,
|
|
283
295
|
homeWins,
|
|
284
296
|
draws,
|
|
@@ -306,23 +318,27 @@ export class APIFootballProvider extends APIProviderBase {
|
|
|
306
318
|
// Try to find Asian Handicap odds
|
|
307
319
|
const ahBet = bookmaker?.bets?.find((b) => b.name?.toLowerCase().includes('asian') ||
|
|
308
320
|
b.name?.toLowerCase().includes('handicap'));
|
|
309
|
-
if (ahBet?.values?.
|
|
310
|
-
const
|
|
321
|
+
if (ahBet?.values?.length >= 2) {
|
|
322
|
+
const homeValue = ahBet.values.find((v) => v.value?.toLowerCase().includes('home') || v.value === 'Home');
|
|
323
|
+
const awayValue = ahBet.values.find((v) => v.value?.toLowerCase().includes('away') || v.value === 'Away');
|
|
324
|
+
const lineMatch = ahBet.values[0].value?.match(/[-+]?\d+(?:\.\d+)?/);
|
|
311
325
|
result.asianHandicap = {
|
|
312
|
-
home: parseFloat(
|
|
313
|
-
away: parseFloat(
|
|
314
|
-
line: parseFloat(
|
|
326
|
+
home: parseFloat(homeValue?.odd) || parseFloat(ahBet.values[0]?.odd) || 2.0,
|
|
327
|
+
away: parseFloat(awayValue?.odd) || parseFloat(ahBet.values[1]?.odd) || 2.0,
|
|
328
|
+
line: lineMatch ? parseFloat(lineMatch[0]) : 0,
|
|
315
329
|
};
|
|
316
330
|
}
|
|
317
331
|
// Try to find Over/Under odds
|
|
318
|
-
const ouBet = bookmaker?.bets?.find((b) => b.name?.toLowerCase().includes('over/under')
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const
|
|
332
|
+
const ouBet = bookmaker?.bets?.find((b) => b.name?.toLowerCase().includes('over/under') ||
|
|
333
|
+
b.name?.toLowerCase().includes('goals over/under'));
|
|
334
|
+
if (ouBet?.values?.length >= 2) {
|
|
335
|
+
const overValue = ouBet.values.find((v) => v.value?.toLowerCase().includes('over'));
|
|
336
|
+
const underValue = ouBet.values.find((v) => v.value?.toLowerCase().includes('under'));
|
|
337
|
+
const lineMatch = overValue?.value?.match(/\d+(?:\.\d+)?/);
|
|
322
338
|
result.overUnder = {
|
|
323
|
-
line,
|
|
324
|
-
over: parseFloat(
|
|
325
|
-
under: parseFloat(
|
|
339
|
+
line: lineMatch ? parseFloat(lineMatch[0]) : 2.5,
|
|
340
|
+
over: parseFloat(overValue?.odd) || 2.0,
|
|
341
|
+
under: parseFloat(underValue?.odd) || 2.0,
|
|
326
342
|
};
|
|
327
343
|
}
|
|
328
344
|
return result;
|
|
@@ -353,7 +369,10 @@ export class FootballDataProvider extends APIProviderBase {
|
|
|
353
369
|
return response;
|
|
354
370
|
}
|
|
355
371
|
async getLiveMatches(leagueId) {
|
|
356
|
-
const
|
|
372
|
+
const endpoint = leagueId
|
|
373
|
+
? `/competitions/${leagueId}/matches?status=IN_PLAY`
|
|
374
|
+
: '/matches?status=IN_PLAY';
|
|
375
|
+
const response = await this.callAPI(endpoint);
|
|
357
376
|
if (response.success && response.data?.matches) {
|
|
358
377
|
const matches = response.data.matches.map((m) => this.transformMatchFD(m));
|
|
359
378
|
return { success: true, data: matches, provider: 'football-data' };
|
|
@@ -467,7 +486,7 @@ export class FootballDataProvider extends APIProviderBase {
|
|
|
467
486
|
drawn: data.homeGames.draw,
|
|
468
487
|
lost: data.homeGames.lost,
|
|
469
488
|
goalsFor: data.homeGames.goalsFor,
|
|
470
|
-
|
|
489
|
+
goalsAgainst: data.homeGames.goalsAgainst,
|
|
471
490
|
} : undefined,
|
|
472
491
|
awayRecord: data.awayGames ? {
|
|
473
492
|
played: data.awayGames.playedGames,
|
|
@@ -475,7 +494,7 @@ export class FootballDataProvider extends APIProviderBase {
|
|
|
475
494
|
drawn: data.awayGames.draw,
|
|
476
495
|
lost: data.awayGames.lost,
|
|
477
496
|
goalsFor: data.awayGames.goalsFor,
|
|
478
|
-
|
|
497
|
+
goalsAgainst: data.awayGames.goalsAgainst,
|
|
479
498
|
} : undefined,
|
|
480
499
|
};
|
|
481
500
|
}
|
|
@@ -502,7 +521,71 @@ export class SportsDBProvider extends APIProviderBase {
|
|
|
502
521
|
}
|
|
503
522
|
getAuthHeaders() {
|
|
504
523
|
// TheSportsDB uses API key in URL, not headers
|
|
505
|
-
return {
|
|
524
|
+
return {
|
|
525
|
+
'Accept': 'application/json',
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Override callAPI to inject API key in URL for TheSportsDB
|
|
530
|
+
*/
|
|
531
|
+
async callAPI(endpoint, options = {}) {
|
|
532
|
+
// Inject API key into URL for TheSportsDB
|
|
533
|
+
const separator = endpoint.includes('?') ? '&' : '?';
|
|
534
|
+
const urlWithKey = `${this.baseUrl}${endpoint}${separator}api_key=${encodeURIComponent(this.apiKey)}`;
|
|
535
|
+
// Check rate limit
|
|
536
|
+
if (this.isRateLimited()) {
|
|
537
|
+
return {
|
|
538
|
+
success: false,
|
|
539
|
+
error: `Rate limited until ${this.rateLimitUntil}`,
|
|
540
|
+
rateLimited: true,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
// Check availability
|
|
544
|
+
if (!this.isAvailable()) {
|
|
545
|
+
return {
|
|
546
|
+
success: false,
|
|
547
|
+
error: 'Provider not available (missing API key)',
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
try {
|
|
551
|
+
const response = await fetch(urlWithKey, {
|
|
552
|
+
...options,
|
|
553
|
+
headers: {
|
|
554
|
+
'Accept': 'application/json',
|
|
555
|
+
...options.headers,
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
this.lastCall = new Date();
|
|
559
|
+
this.callCount++;
|
|
560
|
+
if (!response.ok) {
|
|
561
|
+
if (response.status === 429) {
|
|
562
|
+
const retryAfter = response.headers.get('Retry-After');
|
|
563
|
+
this.rateLimitUntil = new Date(Date.now() + (retryAfter ? parseInt(retryAfter) * 1000 : 60000));
|
|
564
|
+
return {
|
|
565
|
+
success: false,
|
|
566
|
+
error: 'Rate limited',
|
|
567
|
+
rateLimited: true,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
success: false,
|
|
572
|
+
error: `HTTP ${response.status}: ${response.statusText}`,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
const data = await response.json();
|
|
576
|
+
return {
|
|
577
|
+
success: true,
|
|
578
|
+
data: data,
|
|
579
|
+
provider: this.type,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
logger.error(`${this.type} API error: ${error}`);
|
|
584
|
+
return {
|
|
585
|
+
success: false,
|
|
586
|
+
error: error instanceof Error ? error.message : String(error),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
506
589
|
}
|
|
507
590
|
transformResponse(data) {
|
|
508
591
|
return data;
|
|
@@ -8,19 +8,6 @@ import { calculateValue, calculateKelly, calculateRecommendedStake, oddsToProbab
|
|
|
8
8
|
import { formatOdds } from '../utils/formatter.js';
|
|
9
9
|
import { BETTING } from '../core/constants.js';
|
|
10
10
|
// ============= Helper Functions =============
|
|
11
|
-
/**
|
|
12
|
-
* Get league ID from league name
|
|
13
|
-
*/
|
|
14
|
-
function getLeagueId(leagueName) {
|
|
15
|
-
const normalized = leagueName.toLowerCase();
|
|
16
|
-
for (const [key, value] of Object.entries({ ...BETTING })) {
|
|
17
|
-
// Skip non-league entries
|
|
18
|
-
if (typeof value !== 'object' || value === null || !('name' in value)) {
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return undefined;
|
|
23
|
-
}
|
|
24
11
|
/**
|
|
25
12
|
* Search for odds by match
|
|
26
13
|
*/
|
|
@@ -159,13 +146,26 @@ Shows: Fair odds, market odds, value %, confidence, Kelly stake.`, {
|
|
|
159
146
|
break;
|
|
160
147
|
case 'fractional':
|
|
161
148
|
// Parse fractional odds like "5/2" or "5-2"
|
|
162
|
-
const oddsStr = odds.toString();
|
|
149
|
+
const oddsStr = odds.toString().trim();
|
|
163
150
|
const parts = oddsStr.split(/[-/]/);
|
|
164
151
|
if (parts.length === 2) {
|
|
165
|
-
|
|
152
|
+
const numerator = parseFloat(parts[0]);
|
|
153
|
+
const denominator = parseFloat(parts[1]);
|
|
154
|
+
if (!isNaN(numerator) && !isNaN(denominator) && denominator !== 0) {
|
|
155
|
+
decimalOdds = (numerator / denominator) + 1;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
return {
|
|
159
|
+
content: [{ type: 'text', text: `Error: Invalid fractional odds format "${odds}". Use format like "5/2" or "3/1".` }],
|
|
160
|
+
isError: true,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
166
163
|
}
|
|
167
164
|
else {
|
|
168
|
-
|
|
165
|
+
return {
|
|
166
|
+
content: [{ type: 'text', text: `Error: Invalid fractional odds format "${odds}". Use format like "5/2" or "3/1".` }],
|
|
167
|
+
isError: true,
|
|
168
|
+
};
|
|
169
169
|
}
|
|
170
170
|
break;
|
|
171
171
|
}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SPORTS MODULE TOOLS - League Tools
|
|
3
3
|
* Tools for league standings and statistics
|
|
4
|
-
*
|
|
5
|
-
* This file will contain:
|
|
6
|
-
* - get_league_standings
|
|
7
|
-
* - get_top_scorers
|
|
8
|
-
*
|
|
9
|
-
* Status: Placeholder for Phase 2 implementation
|
|
10
4
|
*/
|
|
11
|
-
|
|
5
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
+
export declare function registerLeagueTools(server: McpServer): void;
|
|
@@ -1,12 +1,202 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SPORTS MODULE TOOLS - League Tools
|
|
3
3
|
* Tools for league standings and statistics
|
|
4
|
-
*
|
|
5
|
-
* This file will contain:
|
|
6
|
-
* - get_league_standings
|
|
7
|
-
* - get_top_scorers
|
|
8
|
-
*
|
|
9
|
-
* Status: Placeholder for Phase 2 implementation
|
|
10
4
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { createAPIProvider } from '../providers/api.js';
|
|
7
|
+
import { getSearchProvider } from '../providers/search.js';
|
|
8
|
+
import { getGlobalCache, CacheService } from '../core/cache.js';
|
|
9
|
+
import { formatStandingsTable } from '../utils/formatter.js';
|
|
10
|
+
import { CACHE_CONFIG, LEAGUES } from '../core/constants.js';
|
|
11
|
+
/**
|
|
12
|
+
* Get league ID from league name
|
|
13
|
+
*/
|
|
14
|
+
function getLeagueId(leagueName) {
|
|
15
|
+
const normalized = leagueName.toLowerCase();
|
|
16
|
+
for (const [key, value] of Object.entries(LEAGUES)) {
|
|
17
|
+
if (value.name.toLowerCase().includes(normalized) ||
|
|
18
|
+
normalized.includes(value.name.toLowerCase()) ||
|
|
19
|
+
value.code?.toLowerCase() === normalized.replace(' ', '')) {
|
|
20
|
+
return value.id;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Try exact match
|
|
24
|
+
for (const [key, value] of Object.entries(LEAGUES)) {
|
|
25
|
+
if (value.name.toLowerCase() === normalized) {
|
|
26
|
+
return value.id;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
// ============= Tool Registration =============
|
|
32
|
+
export function registerLeagueTools(server) {
|
|
33
|
+
/**
|
|
34
|
+
* Tool 1: get_league_standings
|
|
35
|
+
* Get current league standings table
|
|
36
|
+
*/
|
|
37
|
+
server.tool('get_league_standings_v2', `Get current league standings table.
|
|
38
|
+
Includes: position, played, won, drawn, lost, goals for/against, goal difference, points.
|
|
39
|
+
Supports home/away splits.`, {
|
|
40
|
+
league: z.string().describe('League name (e.g., "Premier League", "La Liga")'),
|
|
41
|
+
season: z.string().optional().describe('Season year (default: current)'),
|
|
42
|
+
}, async ({ league, season }) => {
|
|
43
|
+
const cache = getGlobalCache();
|
|
44
|
+
const currentYear = new Date().getFullYear();
|
|
45
|
+
const seasonYear = season || currentYear.toString();
|
|
46
|
+
const cacheKey = CacheService.generateKey('standings', league, seasonYear);
|
|
47
|
+
// Check cache first (30min TTL)
|
|
48
|
+
const cached = cache.get(cacheKey);
|
|
49
|
+
if (cached) {
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: 'text', text: cached + '\n\n*(cached data)*' }],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const leagueId = getLeagueId(league);
|
|
55
|
+
let output = '';
|
|
56
|
+
const errors = [];
|
|
57
|
+
if (!leagueId) {
|
|
58
|
+
errors.push(`League "${league}" not found. Available leagues: ${Object.values(LEAGUES).map(l => l.name).join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
if (leagueId) {
|
|
61
|
+
try {
|
|
62
|
+
const api = createAPIProvider();
|
|
63
|
+
const result = await api.getStandings(leagueId.toString(), seasonYear);
|
|
64
|
+
if (result.success && result.data) {
|
|
65
|
+
output = `## 🏆 ${league} - Standings ${seasonYear}\n\n`;
|
|
66
|
+
output += formatStandingsTable(result.data);
|
|
67
|
+
// Cache for 30 minutes
|
|
68
|
+
cache.set(cacheKey, output, CACHE_CONFIG.TTL.STANDINGS);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
errors.push(result.error || 'Unknown API error');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Fallback to web search if API failed
|
|
79
|
+
if (!output && errors.length > 0) {
|
|
80
|
+
try {
|
|
81
|
+
const searchQuery = `${league} standings table ${seasonYear}`;
|
|
82
|
+
const searchProvider = getSearchProvider();
|
|
83
|
+
const searchResults = await searchProvider.search(searchQuery, undefined, 3);
|
|
84
|
+
const fallbackText = searchResults.map(r => `- [${r.title}](${r.url}): ${r.snippet}`).join('\n');
|
|
85
|
+
output = `## ${league} - Standings ${seasonYear}\n\n`;
|
|
86
|
+
output += `*API providers not available. Click links for standings:*\n\n${fallbackText}`;
|
|
87
|
+
}
|
|
88
|
+
catch (searchError) {
|
|
89
|
+
output = `Error: ${errors.join(', ')}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: 'text', text: output }],
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* Tool 2: get_top_scorers
|
|
98
|
+
* Get top scorers for a league
|
|
99
|
+
*/
|
|
100
|
+
server.tool('get_top_scorers_v2', `Get top scorers for a league.`, {
|
|
101
|
+
league: z.string().describe('League name'),
|
|
102
|
+
limit: z.number().optional().default(20).describe('Number of players to return'),
|
|
103
|
+
season: z.string().optional().describe('Season year (default: current)'),
|
|
104
|
+
}, async ({ league, limit, season }) => {
|
|
105
|
+
const cache = getGlobalCache();
|
|
106
|
+
const currentYear = new Date().getFullYear();
|
|
107
|
+
const seasonYear = season || currentYear.toString();
|
|
108
|
+
const cacheKey = CacheService.generateKey('scorers', league, seasonYear, limit.toString());
|
|
109
|
+
// Check cache first
|
|
110
|
+
const cached = cache.get(cacheKey);
|
|
111
|
+
if (cached) {
|
|
112
|
+
return {
|
|
113
|
+
content: [{ type: 'text', text: cached + '\n\n*(cached data)*' }],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const leagueId = getLeagueId(league);
|
|
117
|
+
let output = '';
|
|
118
|
+
const errors = [];
|
|
119
|
+
if (!leagueId) {
|
|
120
|
+
errors.push(`League "${league}" not found`);
|
|
121
|
+
}
|
|
122
|
+
// Try API first
|
|
123
|
+
if (leagueId) {
|
|
124
|
+
try {
|
|
125
|
+
const api = createAPIProvider();
|
|
126
|
+
const searchQuery = `${league} top scorers ${seasonYear}`;
|
|
127
|
+
const searchProvider = getSearchProvider();
|
|
128
|
+
const searchResults = await searchProvider.search(searchQuery, undefined, limit);
|
|
129
|
+
output = `## 🥇 ${league} - Top Scorers ${seasonYear}\n\n`;
|
|
130
|
+
if (searchResults.length > 0) {
|
|
131
|
+
const items = searchResults.map((r, i) => `${i + 1}. [${r.title}](${r.url}): ${r.snippet}`).join('\n');
|
|
132
|
+
output += items;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
output += 'No results found.';
|
|
136
|
+
}
|
|
137
|
+
// Cache the results
|
|
138
|
+
cache.set(cacheKey, output, CACHE_CONFIG.TTL.TEAM_STATS);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Fallback
|
|
145
|
+
if (!output) {
|
|
146
|
+
try {
|
|
147
|
+
const searchQuery = `${league} top scorers ${seasonYear}`;
|
|
148
|
+
const searchProvider = getSearchProvider();
|
|
149
|
+
const searchResults = await searchProvider.search(searchQuery, undefined, limit);
|
|
150
|
+
output = `## 🥇 ${league} - Top Scorers ${seasonYear}\n\n`;
|
|
151
|
+
output += searchResults.map((r, i) => `${i + 1}. [${r.title}](${r.url}): ${r.snippet}`).join('\n');
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
output = `Error: ${errors.join(', ')}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
content: [{ type: 'text', text: output }],
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
/**
|
|
162
|
+
* Tool 3: get_league_fixtures
|
|
163
|
+
* Get upcoming fixtures for a league
|
|
164
|
+
*/
|
|
165
|
+
server.tool('get_league_fixtures', `Get upcoming fixtures for a league.`, {
|
|
166
|
+
league: z.string().describe('League name'),
|
|
167
|
+
days: z.number().optional().default(7).describe('Number of days ahead'),
|
|
168
|
+
}, async ({ league, days }) => {
|
|
169
|
+
const cache = getGlobalCache();
|
|
170
|
+
const cacheKey = CacheService.generateKey('fixtures', league, days.toString());
|
|
171
|
+
// Check cache first
|
|
172
|
+
const cached = cache.get(cacheKey);
|
|
173
|
+
if (cached) {
|
|
174
|
+
return {
|
|
175
|
+
content: [{ type: 'text', text: cached + '\n\n*(cached data)*' }],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const leagueId = getLeagueId(league);
|
|
179
|
+
let output = '';
|
|
180
|
+
try {
|
|
181
|
+
const searchQuery = `${league} fixtures schedule next ${days} days`;
|
|
182
|
+
const searchProvider = getSearchProvider();
|
|
183
|
+
const searchResults = await searchProvider.search(searchQuery, undefined, 10);
|
|
184
|
+
output = `## 📅 ${league} - Upcoming Fixtures\n\n`;
|
|
185
|
+
if (searchResults.length > 0) {
|
|
186
|
+
const items = searchResults.map((r, i) => `${i + 1}. [${r.title}](${r.url}): ${r.snippet}`).join('\n\n');
|
|
187
|
+
output += items;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
output += 'No fixtures found.';
|
|
191
|
+
}
|
|
192
|
+
// Cache for short period
|
|
193
|
+
cache.set(cacheKey, output, 15 * 60 * 1000); // 15 minutes
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
output = `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
content: [{ type: 'text', text: output }],
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
}
|