@gotza02/sequential-thinking 10000.1.2 → 10000.1.4

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.
Files changed (32) hide show
  1. package/README.md +39 -249
  2. package/dist/dashboard/server.d.ts +2 -45
  3. package/dist/dashboard/server.js +64 -250
  4. package/dist/index.js +4 -1
  5. package/dist/tools/sports/core/base.d.ts +31 -1
  6. package/dist/tools/sports/core/base.js +25 -0
  7. package/dist/tools/sports/core/cache.d.ts +8 -19
  8. package/dist/tools/sports/core/cache.js +38 -95
  9. package/dist/tools/sports/core/constants.d.ts +4 -63
  10. package/dist/tools/sports/core/constants.js +11 -86
  11. package/dist/tools/sports/core/types.d.ts +146 -34
  12. package/dist/tools/sports/providers/api.d.ts +12 -1
  13. package/dist/tools/sports/providers/api.js +170 -0
  14. package/dist/tools/sports/tools/match.d.ts +5 -0
  15. package/dist/tools/sports/tools/match.js +530 -5
  16. package/package.json +1 -1
  17. package/dist/tools/sports/core/alert-manager.d.ts +0 -96
  18. package/dist/tools/sports/core/alert-manager.js +0 -319
  19. package/dist/tools/sports/core/circuit-breaker.d.ts +0 -40
  20. package/dist/tools/sports/core/circuit-breaker.js +0 -99
  21. package/dist/tools/sports/core/data-quality.d.ts +0 -36
  22. package/dist/tools/sports/core/data-quality.js +0 -243
  23. package/dist/tools/sports/core/historical-analyzer.d.ts +0 -54
  24. package/dist/tools/sports/core/historical-analyzer.js +0 -261
  25. package/dist/tools/sports/core/index.d.ts +0 -13
  26. package/dist/tools/sports/core/index.js +0 -16
  27. package/dist/tools/sports/core/ml-prediction.d.ts +0 -76
  28. package/dist/tools/sports/core/ml-prediction.js +0 -260
  29. package/dist/tools/sports/core/realtime-manager.d.ts +0 -51
  30. package/dist/tools/sports/core/realtime-manager.js +0 -222
  31. package/dist/tools/sports/core/retry.d.ts +0 -29
  32. package/dist/tools/sports/core/retry.js +0 -77
@@ -14,49 +14,31 @@ export const API_CONFIG = {
14
14
  // TheSportsDB
15
15
  SPORTS_DB_KEY: process.env.SPORTS_DB_KEY || '',
16
16
  SPORTS_DB_BASE: 'https://www.thesportsdb.com/api/v1/json',
17
- // Odds API
18
- ODDS_API_KEY: process.env.ODDS_API_KEY || '',
19
- ODDS_API_BASE: 'https://api.the-odds-api.com/v4',
20
- // SportRadar (requires enterprise license)
21
- SPORTRADAR_KEY: process.env.SPORTRADAR_KEY || '',
22
- SPORTRADAR_BASE: 'https://api.sportradar.us/soccer/trial/v4',
23
17
  // Rate limits (requests per minute)
24
18
  RATE_LIMITS: {
25
19
  API_FOOTBALL: 10,
26
20
  FOOTBALL_DATA: 10,
27
21
  SPORTS_DB: 30,
28
- ODDS_API: 20,
29
- SPORTRADAR: 100,
30
22
  }
31
23
  };
32
24
  // ============= Cache Configuration =============
33
25
  export const CACHE_CONFIG = {
34
26
  // Default TTL in milliseconds
35
27
  DEFAULT_TTL: parseInt(process.env.SPORTS_CACHE_TTL || '300000'), // 5 minutes
36
- // Specific TTLs for different data types (OPTIMIZED FOR REALTIME)
28
+ // Specific TTLs for different data types
37
29
  TTL: {
38
- LIVE_SCORES: 15 * 1000, // 15 seconds (was 1 minute)
39
- LIVE_EVENTS: 5 * 1000, // 5 seconds for live match events
40
- ODDS: 30 * 1000, // 30 seconds (was 2 minutes)
41
- MATCH_DETAILS: 2 * 60 * 1000, // 2 minutes (was 5 minutes)
42
- STANDINGS: 15 * 60 * 1000, // 15 minutes (was 30 minutes)
43
- TEAM_STATS: 30 * 60 * 1000, // 30 minutes (was 1 hour)
44
- PLAYER_STATS: 30 * 60 * 1000, // 30 minutes (was 1 hour)
45
- TRANSFER_NEWS: 10 * 60 * 1000, // 10 minutes (was 15 minutes)
46
- PREDICTIONS: 60 * 60 * 1000, // 1 hour
47
- },
48
- // Stale-while-revalidate multipliers
49
- STALE_MULTIPLIERS: {
50
- LIVE_SCORES: 2, // Stale for 30 seconds (15s * 2)
51
- ODDS: 2, // Stale for 1 minute (30s * 2)
52
- MATCH_DETAILS: 2, // Stale for 4 minutes
53
- STANDINGS: 1.5, // Stale for 22.5 minutes
54
- TEAM_STATS: 1.5, // Stale for 45 minutes
30
+ LIVE_SCORES: 60 * 1000, // 1 minute
31
+ MATCH_DETAILS: 5 * 60 * 1000, // 5 minutes
32
+ STANDINGS: 30 * 60 * 1000, // 30 minutes
33
+ TEAM_STATS: 60 * 60 * 1000, // 1 hour
34
+ PLAYER_STATS: 60 * 60 * 1000, // 1 hour
35
+ ODDS: 2 * 60 * 1000, // 2 minutes
36
+ TRANSFER_NEWS: 15 * 60 * 1000, // 15 minutes
55
37
  },
56
38
  // Cache file path
57
39
  CACHE_PATH: process.env.SPORTS_CACHE_PATH || '.sports_cache.json',
58
40
  // Max cache size (number of entries)
59
- MAX_SIZE: 2000,
41
+ MAX_SIZE: 1000,
60
42
  };
61
43
  // ============= Scraping Configuration =============
62
44
  export const SCRAPER_CONFIG = {
@@ -170,67 +152,10 @@ export const ERRORS = {
170
152
  TIMEOUT: 'Request timed out.',
171
153
  INVALID_RESPONSE: 'Invalid response from API.',
172
154
  ALL_PROVIDERS_FAILED: 'All data providers failed.',
173
- CIRCUIT_BREAKER_OPEN: 'Service temporarily unavailable due to repeated failures.',
174
- };
175
- // ============= Alert Configuration =============
176
- export const ALERT_CONFIG = {
177
- // Check interval for alerts (milliseconds)
178
- CHECK_INTERVAL: 10000, // 10 seconds
179
- // Default cooldown between same alert (milliseconds)
180
- DEFAULT_COOLDOWN: 300000, // 5 minutes
181
- // Max alerts per hour per rule
182
- MAX_ALERTS_PER_HOUR: 10,
183
- // Alert types
184
- TYPES: {
185
- ODDS_DROP: 'odds_drop',
186
- ODDS_VALUE: 'odds_value',
187
- GOAL: 'goal',
188
- RED_CARD: 'red_card',
189
- LINEUP_CHANGE: 'lineup_change',
190
- MATCH_START: 'match_start',
191
- MATCH_END: 'match_end',
192
- CUSTOM: 'custom',
193
- },
194
- };
195
- // ============= Realtime Configuration =============
196
- export const REALTIME_CONFIG = {
197
- // WebSocket reconnection
198
- WS_RECONNECT_INTERVAL: 5000,
199
- WS_MAX_RECONNECT_ATTEMPTS: 10,
200
- // Polling intervals (fallback when WebSocket unavailable)
201
- POLLING_INTERVALS: {
202
- LIVE_SCORES: 15000, // 15 seconds
203
- ODDS: 30000, // 30 seconds
204
- MATCH_EVENTS: 5000, // 5 seconds
205
- },
206
- // Event batching
207
- EVENT_BATCH_SIZE: 10,
208
- EVENT_BATCH_TIMEOUT: 1000,
209
- };
210
- // ============= ML Prediction Configuration =============
211
- export const ML_CONFIG = {
212
- // Minimum matches for training
213
- MIN_TRAINING_MATCHES: 50,
214
- // Feature weights
215
- FEATURE_WEIGHTS: {
216
- form: 0.25,
217
- h2h: 0.20,
218
- home_advantage: 0.15,
219
- xg: 0.15,
220
- injuries: 0.10,
221
- fatigue: 0.10,
222
- weather: 0.05,
223
- },
224
- // Confidence thresholds
225
- CONFIDENCE_THRESHOLDS: {
226
- high: 0.70,
227
- medium: 0.55,
228
- low: 0.40,
229
- },
230
155
  };
231
156
  // ============= Version Info =============
232
157
  export const MODULE_INFO = {
233
158
  NAME: 'sports-module',
234
- VERSION: '3.0.0',
235
- DESCRIPTION: 'Football Intelligence System for MCP Server - Realtime Edition',
159
+ VERSION: '2.0.0',
160
+ DESCRIPTION: 'Football Intelligence System for MCP Server',
236
161
  };
@@ -7,7 +7,7 @@ export type MatchStatus = 'scheduled' | 'live' | 'finished' | 'postponed' | 'can
7
7
  export type BetType = 'home' | 'draw' | 'away' | 'over' | 'under' | 'btts' | 'handicap';
8
8
  export type KellyVariant = 'full' | 'half' | 'quarter';
9
9
  export type QueryType = 'general' | 'h2h' | 'form' | 'news' | 'stats' | 'odds' | 'referee' | 'weather' | 'fatigue' | 'setpieces';
10
- export type ProviderType = 'api-football' | 'football-data' | 'sports-db' | 'odds-api' | 'sportradar' | 'scraper';
10
+ export type ProviderType = 'api-football' | 'football-data' | 'sports-db' | 'scraper';
11
11
  export interface SearchQuery {
12
12
  type: QueryType;
13
13
  query: string;
@@ -175,8 +175,6 @@ export interface Match {
175
175
  homeSubstitutes?: Player[];
176
176
  awaySubstitutes?: Player[];
177
177
  events?: MatchEvent[];
178
- dataQuality?: number;
179
- lastUpdated?: Date;
180
178
  }
181
179
  export interface MatchOdds {
182
180
  homeWin: number;
@@ -346,7 +344,6 @@ export interface APIResponse<T = any> {
346
344
  provider?: ProviderType;
347
345
  cached?: boolean;
348
346
  rateLimited?: boolean;
349
- quality?: number;
350
347
  }
351
348
  export interface ProviderStatus {
352
349
  name: ProviderType;
@@ -355,7 +352,6 @@ export interface ProviderStatus {
355
352
  quotaLimit: number;
356
353
  rateLimitUntil?: Date;
357
354
  lastCall?: Date;
358
- circuitBreakerState?: 'closed' | 'open' | 'half-open';
359
355
  }
360
356
  export interface ComparisonResult {
361
357
  teamA: Team;
@@ -381,38 +377,154 @@ export interface LiveScoreUpdate {
381
377
  events: MatchEvent[];
382
378
  timestamp: Date;
383
379
  }
384
- export interface MLPrediction {
385
- matchId: string;
386
- homeWinProbability: number;
387
- drawProbability: number;
388
- awayWinProbability: number;
389
- over25Probability?: number;
390
- bttsProbability?: number;
380
+ /**
381
+ * Probability range with uncertainty quantification
382
+ */
383
+ export interface ProbabilityRange {
384
+ low: number;
385
+ mid: number;
386
+ high: number;
387
+ }
388
+ /**
389
+ * Data quality assessment
390
+ */
391
+ export interface DataQuality {
392
+ score: number;
393
+ factors: {
394
+ hasAPIData: boolean;
395
+ hasRecentData: boolean;
396
+ sampleSize: 'small' | 'medium' | 'large';
397
+ dataAge: number;
398
+ missingDataPoints: string[];
399
+ };
400
+ }
401
+ /**
402
+ * Value bet recommendation with full analysis
403
+ */
404
+ export interface ValueBetRecommendation {
405
+ selection: string;
406
+ market: '1x2' | 'over_under' | 'btts' | 'asian_handicap';
407
+ odds: number;
408
+ fairOdds: number;
409
+ value: number;
391
410
  confidence: number;
392
- factors: PredictionFactor[];
393
- timestamp: Date;
411
+ kellyFraction: number;
412
+ recommendedStake: number;
413
+ reasoning: string;
394
414
  }
395
- export interface PredictionFactor {
396
- name: string;
397
- weight: number;
398
- impact: 'positive' | 'negative' | 'neutral';
415
+ /**
416
+ * Game scenario for skeptic analysis
417
+ */
418
+ export interface GameScenario {
419
+ trigger: string;
420
+ probability: number;
421
+ impact: 'high' | 'medium' | 'low';
399
422
  description: string;
400
423
  }
401
- export interface HistoricalPattern {
402
- pattern: string;
403
- occurrence: number;
404
- successRate: number;
405
- avgOdds: number;
406
- profit: number;
424
+ /**
425
+ * Fatigue assessment
426
+ */
427
+ export interface FatigueAssessment {
428
+ score: number;
429
+ daysRest: number;
430
+ travelDistance?: number;
431
+ squadDepth: number;
432
+ keyInjuries: string[];
407
433
  }
408
- export interface TeamHistoricalPerformance {
409
- team: Team;
410
- totalMatches: number;
411
- wins: number;
412
- draws: number;
413
- losses: number;
414
- avgGoalsFor: number;
415
- avgGoalsAgainst: number;
416
- homeAdvantage: number;
417
- patterns: HistoricalPattern[];
434
+ /**
435
+ * Six-persona analysis output structure
436
+ */
437
+ export interface SixPersonaAnalysis {
438
+ dataScientist: {
439
+ homeWinProbability: ProbabilityRange;
440
+ drawProbability: ProbabilityRange;
441
+ awayWinProbability: ProbabilityRange;
442
+ expectedGoals: {
443
+ home: number;
444
+ away: number;
445
+ };
446
+ keyStats: ComparisonMetric[];
447
+ };
448
+ tacticalScout: {
449
+ homeFormation?: string;
450
+ awayFormation?: string;
451
+ styleMatchup: string;
452
+ keyBattles: string[];
453
+ tacticalAdvantage: 'home' | 'away' | 'neutral';
454
+ };
455
+ physio: {
456
+ homeFatigue: FatigueAssessment;
457
+ awayFatigue: FatigueAssessment;
458
+ injuryImpact: string;
459
+ };
460
+ setPieceAnalyst: {
461
+ homeStrength: number;
462
+ awayStrength: number;
463
+ predictedCorners: number;
464
+ aerialAdvantage: 'home' | 'away' | 'neutral';
465
+ };
466
+ insider: {
467
+ marketMovement: string;
468
+ weatherImpact?: string;
469
+ refereeImpact?: string;
470
+ managerialContext?: string;
471
+ };
472
+ skeptic: {
473
+ upsetRisk: number;
474
+ trapIndicators: string[];
475
+ gameScenarios: GameScenario[];
476
+ };
477
+ }
478
+ /**
479
+ * Complete match analysis result (structured output)
480
+ */
481
+ export interface MatchAnalysisResult {
482
+ match: {
483
+ homeTeam: string;
484
+ awayTeam: string;
485
+ league?: string;
486
+ date?: Date;
487
+ matchId?: string;
488
+ };
489
+ dataQuality: DataQuality;
490
+ analysis: SixPersonaAnalysis;
491
+ predictions: {
492
+ matchResult: {
493
+ homeWin: ProbabilityRange;
494
+ draw: ProbabilityRange;
495
+ awayWin: ProbabilityRange;
496
+ };
497
+ overUnder25: {
498
+ over: ProbabilityRange;
499
+ under: ProbabilityRange;
500
+ };
501
+ btts: {
502
+ yes: ProbabilityRange;
503
+ no: ProbabilityRange;
504
+ };
505
+ };
506
+ valueBets: ValueBetRecommendation[];
507
+ verdict: {
508
+ summary: string;
509
+ bestBet: string;
510
+ confidence: number;
511
+ riskLevel: 'low' | 'medium' | 'high';
512
+ };
513
+ metadata: {
514
+ generatedAt: Date;
515
+ dataSources: string[];
516
+ };
517
+ }
518
+ /**
519
+ * API Match data bundle
520
+ */
521
+ export interface APIMatchData {
522
+ match?: Match;
523
+ h2h?: HeadToHead;
524
+ homeForm?: Match[];
525
+ awayForm?: Match[];
526
+ homeStats?: TeamStats;
527
+ awayStats?: TeamStats;
528
+ odds?: MatchOdds;
529
+ lastUpdated?: Date;
418
530
  }
@@ -2,7 +2,7 @@
2
2
  * SPORTS MODULE API PROVIDERS
3
3
  * Concrete implementations of football API providers
4
4
  */
5
- import { Match, Team, Player, TableEntry, APIResponse } from '../core/types.js';
5
+ import { Match, Team, Player, TableEntry, APIResponse, HeadToHead, MatchOdds } from '../core/types.js';
6
6
  import { APIProviderBase, FallbackProvider } from '../core/base.js';
7
7
  /**
8
8
  * API-Football Provider (via RapidAPI)
@@ -19,12 +19,17 @@ export declare class APIFootballProvider extends APIProviderBase {
19
19
  getPlayer(playerId: string): Promise<APIResponse<Player>>;
20
20
  searchTeams(query: string): Promise<APIResponse<Team[]>>;
21
21
  searchPlayers(query: string): Promise<APIResponse<Player[]>>;
22
+ getH2H(matchId: string, team1Id?: string, team2Id?: string): Promise<APIResponse<HeadToHead>>;
23
+ getTeamForm(teamId: string, last?: number): Promise<APIResponse<Match[]>>;
24
+ getMatchOdds(matchId: string): Promise<APIResponse<MatchOdds>>;
22
25
  private transformMatch;
23
26
  private transformTeam;
24
27
  private transformPlayer;
25
28
  private transformTableEntry;
26
29
  private mapStatus;
27
30
  private mapPosition;
31
+ private transformH2H;
32
+ private transformOdds;
28
33
  }
29
34
  /**
30
35
  * Football-Data.org Provider
@@ -41,6 +46,9 @@ export declare class FootballDataProvider extends APIProviderBase {
41
46
  getPlayer(playerId: string): Promise<APIResponse<Player>>;
42
47
  searchTeams(query: string): Promise<APIResponse<Team[]>>;
43
48
  searchPlayers(query: string): Promise<APIResponse<Player[]>>;
49
+ getH2H(matchId: string, team1Id?: string, team2Id?: string): Promise<APIResponse<HeadToHead>>;
50
+ getTeamForm(teamId: string, last?: number): Promise<APIResponse<Match[]>>;
51
+ getMatchOdds(matchId: string): Promise<APIResponse<MatchOdds>>;
44
52
  private transformMatchFD;
45
53
  private transformTeamFD;
46
54
  private transformTableEntryFD;
@@ -61,6 +69,9 @@ export declare class SportsDBProvider extends APIProviderBase {
61
69
  getPlayer(playerId: string): Promise<APIResponse<Player>>;
62
70
  searchTeams(query: string): Promise<APIResponse<Team[]>>;
63
71
  searchPlayers(query: string): Promise<APIResponse<Player[]>>;
72
+ getH2H(matchId: string, team1Id?: string, team2Id?: string): Promise<APIResponse<HeadToHead>>;
73
+ getTeamForm(teamId: string, last?: number): Promise<APIResponse<Match[]>>;
74
+ getMatchOdds(matchId: string): Promise<APIResponse<MatchOdds>>;
64
75
  private transformMatchDB;
65
76
  private transformTeamDB;
66
77
  private transformPlayerDB;
@@ -88,6 +88,58 @@ export class APIFootballProvider extends APIProviderBase {
88
88
  }
89
89
  return response;
90
90
  }
91
+ // ============= New Methods for Enhanced Analysis =============
92
+ async getH2H(matchId, team1Id, team2Id) {
93
+ const cacheKey = `h2h:api-football:${matchId}`;
94
+ const cached = this.cache.get(cacheKey);
95
+ if (cached)
96
+ return { success: true, data: cached, cached: true };
97
+ // Use team IDs if provided, otherwise fetch match first to get teams
98
+ let homeId = team1Id;
99
+ let awayId = team2Id;
100
+ if (!homeId || !awayId) {
101
+ const matchResult = await this.getMatch(matchId);
102
+ if (!matchResult.success || !matchResult.data) {
103
+ return { success: false, error: 'Could not resolve team IDs for H2H' };
104
+ }
105
+ homeId = matchResult.data.homeTeam.id;
106
+ awayId = matchResult.data.awayTeam.id;
107
+ }
108
+ const response = await this.callAPI(`/fixtures/headtohead?h2h=${homeId}-${awayId}`);
109
+ if (response.success && response.data?.response) {
110
+ const h2h = this.transformH2H(response.data.response, homeId, awayId);
111
+ this.cache.set(cacheKey, h2h);
112
+ return { success: true, data: h2h, provider: 'api-football' };
113
+ }
114
+ return response;
115
+ }
116
+ async getTeamForm(teamId, last = 5) {
117
+ const cacheKey = `form:api-football:${teamId}:${last}`;
118
+ const cached = this.cache.get(cacheKey);
119
+ if (cached)
120
+ return { success: true, data: cached, cached: true };
121
+ const currentYear = new Date().getFullYear();
122
+ const response = await this.callAPI(`/fixtures?team=${teamId}&last=${last}&season=${currentYear}`);
123
+ if (response.success && response.data?.response) {
124
+ const matches = response.data.response.map((m) => this.transformMatch(m));
125
+ this.cache.set(cacheKey, matches);
126
+ return { success: true, data: matches, provider: 'api-football' };
127
+ }
128
+ return response;
129
+ }
130
+ async getMatchOdds(matchId) {
131
+ const cacheKey = `odds:api-football:${matchId}`;
132
+ const cached = this.cache.get(cacheKey);
133
+ if (cached)
134
+ return { success: true, data: cached, cached: true };
135
+ const response = await this.callAPI(`/odds?fixture=${matchId}`);
136
+ if (response.success && response.data?.response?.[0]) {
137
+ const odds = this.transformOdds(response.data.response[0]);
138
+ this.cache.set(cacheKey, odds);
139
+ return { success: true, data: odds, provider: 'api-football' };
140
+ }
141
+ return response;
142
+ }
91
143
  // ============= Transformation Helpers =============
92
144
  transformMatch(data) {
93
145
  return {
@@ -194,6 +246,87 @@ export class APIFootballProvider extends APIProviderBase {
194
246
  return 'FW';
195
247
  return 'SUB';
196
248
  }
249
+ transformH2H(data, homeId, awayId) {
250
+ const matches = data.map(m => this.transformMatch(m));
251
+ let homeWins = 0;
252
+ let draws = 0;
253
+ let awayWins = 0;
254
+ let homeGoals = 0;
255
+ let awayGoals = 0;
256
+ for (const match of matches) {
257
+ if (!match.score)
258
+ continue;
259
+ // Determine which team was home/away in the H2H match
260
+ const isHomeTeam = match.homeTeam.id === homeId;
261
+ if (match.score.home === match.score.away) {
262
+ draws++;
263
+ }
264
+ else if (isHomeTeam) {
265
+ if (match.score.home > match.score.away)
266
+ homeWins++;
267
+ else
268
+ awayWins++;
269
+ }
270
+ else {
271
+ if (match.score.away > match.score.home)
272
+ homeWins++;
273
+ else
274
+ awayWins++;
275
+ }
276
+ homeGoals += isHomeTeam ? match.score.home : match.score.away;
277
+ awayGoals += isHomeTeam ? match.score.away : match.score.home;
278
+ }
279
+ return {
280
+ homeTeam: matches[0]?.homeTeam.id === homeId ? matches[0].homeTeam : matches[0]?.awayTeam,
281
+ awayTeam: matches[0]?.homeTeam.id === awayId ? matches[0].homeTeam : matches[0]?.awayTeam,
282
+ totalMatches: matches.length,
283
+ homeWins,
284
+ draws,
285
+ awayWins,
286
+ homeGoals,
287
+ awayGoals,
288
+ recentMatches: matches.slice(0, 5),
289
+ };
290
+ }
291
+ transformOdds(data) {
292
+ // Find 1x2 odds from the first bookmaker
293
+ const bookmaker = data.bookmakers?.[0];
294
+ const matchWinnerBet = bookmaker?.bets?.find((b) => b.name === 'Match Winner');
295
+ const homeWinOdds = matchWinnerBet?.values?.find((v) => v.value === 'Home')?.odd;
296
+ const drawOdds = matchWinnerBet?.values?.find((v) => v.value === 'Draw')?.odd;
297
+ const awayWinOdds = matchWinnerBet?.values?.find((v) => v.value === 'Away')?.odd;
298
+ // Default odds if not found
299
+ const result = {
300
+ homeWin: parseFloat(homeWinOdds) || 2.0,
301
+ draw: parseFloat(drawOdds) || 3.0,
302
+ awayWin: parseFloat(awayWinOdds) || 2.0,
303
+ provider: bookmaker?.name || 'Unknown',
304
+ timestamp: new Date(data.update || Date.now()),
305
+ };
306
+ // Try to find Asian Handicap odds
307
+ const ahBet = bookmaker?.bets?.find((b) => b.name?.toLowerCase().includes('asian') ||
308
+ b.name?.toLowerCase().includes('handicap'));
309
+ if (ahBet?.values?.[0]) {
310
+ const ahValue = ahBet.values[0];
311
+ result.asianHandicap = {
312
+ home: parseFloat(ahValue.odd) || 2.0,
313
+ away: parseFloat(ahValue.odd) || 2.0,
314
+ line: parseFloat(ahValue.value) || 0,
315
+ };
316
+ }
317
+ // Try to find Over/Under odds
318
+ const ouBet = bookmaker?.bets?.find((b) => b.name?.toLowerCase().includes('over/under'));
319
+ if (ouBet?.values?.[0]) {
320
+ const ouValue = ouBet.values[0];
321
+ const line = parseFloat(ouValue.value?.replace('Over ', '').replace('Under ', '')) || 2.5;
322
+ result.overUnder = {
323
+ line,
324
+ over: parseFloat(ouValue.odd) || 2.0,
325
+ under: parseFloat(ouValue.odd) || 2.0,
326
+ };
327
+ }
328
+ return result;
329
+ }
197
330
  }
198
331
  /**
199
332
  * Football-Data.org Provider
@@ -263,6 +396,25 @@ export class FootballDataProvider extends APIProviderBase {
263
396
  error: 'Search not available via Football-Data.org',
264
397
  };
265
398
  }
399
+ async getH2H(matchId, team1Id, team2Id) {
400
+ return {
401
+ success: false,
402
+ error: 'H2H data not available via Football-Data.org',
403
+ };
404
+ }
405
+ async getTeamForm(teamId, last) {
406
+ // Football-Data.org has limited form data in match endpoints
407
+ return {
408
+ success: false,
409
+ error: 'Team form not directly available via Football-Data.org',
410
+ };
411
+ }
412
+ async getMatchOdds(matchId) {
413
+ return {
414
+ success: false,
415
+ error: 'Odds not available via Football-Data.org',
416
+ };
417
+ }
266
418
  // ============= Transformation Helpers (Football-Data.org format) =============
267
419
  transformMatchFD(data) {
268
420
  return {
@@ -416,6 +568,24 @@ export class SportsDBProvider extends APIProviderBase {
416
568
  }
417
569
  return response;
418
570
  }
571
+ async getH2H(matchId, team1Id, team2Id) {
572
+ return {
573
+ success: false,
574
+ error: 'H2H data not available via TheSportsDB',
575
+ };
576
+ }
577
+ async getTeamForm(teamId, last) {
578
+ return {
579
+ success: false,
580
+ error: 'Team form not directly available via TheSportsDB',
581
+ };
582
+ }
583
+ async getMatchOdds(matchId) {
584
+ return {
585
+ success: false,
586
+ error: 'Odds not available via TheSportsDB',
587
+ };
588
+ }
419
589
  // ============= Transformation Helpers (TheSportsDB format) =============
420
590
  transformMatchDB(data) {
421
591
  return {
@@ -3,8 +3,13 @@
3
3
  * Tools for match analysis and live scores
4
4
  */
5
5
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { MatchAnalysisResult } from '../core/types.js';
6
7
  /**
7
8
  * Perform comprehensive match analysis using web search
8
9
  */
9
10
  export declare function performMatchAnalysis(homeTeam: string, awayTeam: string, league?: string, context?: string): Promise<string>;
11
+ /**
12
+ * Perform structured match analysis with JSON output
13
+ */
14
+ export declare function performStructuredMatchAnalysis(homeTeam: string, awayTeam: string, league?: string, context?: string): Promise<MatchAnalysisResult>;
10
15
  export declare function registerMatchTools(server: McpServer): void;