@gotza02/sequential-thinking 2026.2.39 → 2026.2.41
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 +7 -4
- package/SYSTEM_INSTRUCTION.md +25 -50
- package/dist/tools/sports/core/base.d.ts +169 -0
- package/dist/tools/sports/core/base.js +289 -0
- package/dist/tools/sports/core/cache.d.ts +106 -0
- package/dist/tools/sports/core/cache.js +305 -0
- package/dist/tools/sports/core/constants.d.ts +179 -0
- package/dist/tools/sports/core/constants.js +149 -0
- package/dist/tools/sports/core/types.d.ts +379 -0
- package/dist/tools/sports/core/types.js +5 -0
- package/dist/tools/sports/index.d.ts +34 -0
- package/dist/tools/sports/index.js +50 -0
- package/dist/tools/sports/providers/api.d.ts +73 -0
- package/dist/tools/sports/providers/api.js +517 -0
- package/dist/tools/sports/providers/scraper.d.ts +66 -0
- package/dist/tools/sports/providers/scraper.js +186 -0
- package/dist/tools/sports/providers/search.d.ts +54 -0
- package/dist/tools/sports/providers/search.js +224 -0
- package/dist/tools/sports/tools/betting.d.ts +6 -0
- package/dist/tools/sports/tools/betting.js +251 -0
- package/dist/tools/sports/tools/league.d.ts +11 -0
- package/dist/tools/sports/tools/league.js +12 -0
- package/dist/tools/sports/tools/live.d.ts +9 -0
- package/dist/tools/sports/tools/live.js +235 -0
- package/dist/tools/sports/tools/match.d.ts +6 -0
- package/dist/tools/sports/tools/match.js +323 -0
- package/dist/tools/sports/tools/player.d.ts +6 -0
- package/dist/tools/sports/tools/player.js +152 -0
- package/dist/tools/sports/tools/team.d.ts +6 -0
- package/dist/tools/sports/tools/team.js +370 -0
- package/dist/tools/sports/utils/calculator.d.ts +69 -0
- package/dist/tools/sports/utils/calculator.js +156 -0
- package/dist/tools/sports/utils/formatter.d.ts +57 -0
- package/dist/tools/sports/utils/formatter.js +206 -0
- package/dist/tools/sports.d.ts +7 -0
- package/dist/tools/sports.js +27 -6
- package/package.json +1 -1
- package/system_instruction.md +155 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPORTS MODULE TOOLS - Match Tools
|
|
3
|
+
* Tools for match analysis and live scores
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { createAPIProvider } from '../providers/api.js';
|
|
7
|
+
import { getSearchProvider } from '../providers/search.js';
|
|
8
|
+
import { scrapeMatchContent, findBestMatchUrl } from '../providers/scraper.js';
|
|
9
|
+
import { getGlobalCache, CacheService } from '../core/cache.js';
|
|
10
|
+
import { formatMatchesTable, formatScore, formatMatchStatus } from '../utils/formatter.js';
|
|
11
|
+
import { CACHE_CONFIG, LEAGUES } from '../core/constants.js';
|
|
12
|
+
// ============= Helper Functions =============
|
|
13
|
+
/**
|
|
14
|
+
* Get date context for search queries
|
|
15
|
+
*/
|
|
16
|
+
function getDateContext() {
|
|
17
|
+
const now = new Date();
|
|
18
|
+
const month = now.toLocaleDateString('en-US', { month: 'long' });
|
|
19
|
+
const year = now.getFullYear();
|
|
20
|
+
return ` ${month} ${year}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Extract team names from match analysis result
|
|
24
|
+
*/
|
|
25
|
+
function extractTeamNames(result) {
|
|
26
|
+
const match = result.match(/(?:home|away)[Tt]eam[:\s]+["']?([A-Za-z\s\.]+)["']?/i);
|
|
27
|
+
if (!match)
|
|
28
|
+
return null;
|
|
29
|
+
return { homeTeam: '', awayTeam: '' }; // Will be parsed from context
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Try to find match ID from API by team names and league
|
|
33
|
+
*/
|
|
34
|
+
async function findMatchId(homeTeam, awayTeam, league) {
|
|
35
|
+
try {
|
|
36
|
+
const api = createAPIProvider();
|
|
37
|
+
const searchResult = await api.searchTeams(homeTeam);
|
|
38
|
+
if (searchResult.success && searchResult.data && searchResult.data.length > 0) {
|
|
39
|
+
// For now, return null - actual match ID lookup would require complex logic
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Ignore search errors
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Perform comprehensive match analysis using web search
|
|
50
|
+
*/
|
|
51
|
+
async function performMatchAnalysis(homeTeam, awayTeam, league, context) {
|
|
52
|
+
const leagueStr = league ? `${league} ` : '';
|
|
53
|
+
const baseQuery = `${leagueStr}${homeTeam} vs ${awayTeam}`;
|
|
54
|
+
const dateQuery = getDateContext();
|
|
55
|
+
const searchProvider = getSearchProvider();
|
|
56
|
+
const queries = [
|
|
57
|
+
{ type: 'general', query: `${baseQuery} match prediction stats${dateQuery}`, title: 'General & Prediction' },
|
|
58
|
+
{ type: 'h2h', query: `${baseQuery} head to head history`, title: 'Head to Head' },
|
|
59
|
+
{ type: 'form', query: `${homeTeam} ${awayTeam} recent form last 5 matches${dateQuery}`, title: 'Recent Form' },
|
|
60
|
+
{ type: 'news', query: `${baseQuery} team news injuries lineups${dateQuery}`, title: 'Team News & Lineups' },
|
|
61
|
+
{ type: 'stats', query: `${baseQuery} xG expected goals stats${dateQuery}`, title: 'Advanced Metrics' },
|
|
62
|
+
{ type: 'odds', query: `${baseQuery} odds movement dropping odds${dateQuery}`, title: 'Market & Odds' },
|
|
63
|
+
{ type: 'fatigue', query: `${homeTeam} ${awayTeam} days rest fixture congestion${dateQuery}`, title: 'Fatigue & Schedule' },
|
|
64
|
+
{ type: 'setpieces', query: `${baseQuery} set pieces corners aerial duels${dateQuery}`, title: 'Set Pieces' },
|
|
65
|
+
];
|
|
66
|
+
if (context) {
|
|
67
|
+
queries.push({ type: 'general', query: `${baseQuery} ${context}${dateQuery}`, title: 'Specific Context' });
|
|
68
|
+
}
|
|
69
|
+
let combinedResults = `--- FOOTBALL MATCH DATA: ${homeTeam} vs ${awayTeam} ---\n`;
|
|
70
|
+
combinedResults += `Match Context Date: ${new Date().toLocaleDateString()}\n\n`;
|
|
71
|
+
const candidateUrls = [];
|
|
72
|
+
// Execute searches in parallel (in small batches)
|
|
73
|
+
const batchSize = 4;
|
|
74
|
+
for (let i = 0; i < queries.length; i += batchSize) {
|
|
75
|
+
const batch = queries.slice(i, i + batchSize);
|
|
76
|
+
const batchPromises = batch.map(async (q) => {
|
|
77
|
+
try {
|
|
78
|
+
const results = await searchProvider.search(q.query, undefined, 4);
|
|
79
|
+
results.forEach(r => candidateUrls.push(r.url));
|
|
80
|
+
const items = results.map(r => `- [${r.title}](${r.url}): ${r.snippet}`).join('\n');
|
|
81
|
+
return `### ${q.title}\n${items}\n`;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return `### ${q.title} (Failed)\nError: ${error instanceof Error ? error.message : String(error)}\n`;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const batchResults = await Promise.all(batchPromises);
|
|
88
|
+
combinedResults += batchResults.join('\n');
|
|
89
|
+
}
|
|
90
|
+
// Deep dive: Scrape the best URL
|
|
91
|
+
if (candidateUrls.length > 0) {
|
|
92
|
+
const bestUrl = findBestMatchUrl(candidateUrls);
|
|
93
|
+
if (bestUrl) {
|
|
94
|
+
combinedResults += `\n--- DEEP DIVE ANALYSIS (${bestUrl}) ---\n`;
|
|
95
|
+
try {
|
|
96
|
+
const scrapedContent = await scrapeMatchContent(bestUrl);
|
|
97
|
+
if (scrapedContent.success) {
|
|
98
|
+
combinedResults += scrapedContent.data || '';
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
combinedResults += `(Deep dive failed: ${scrapedContent.error})`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (scrapeError) {
|
|
105
|
+
combinedResults += `(Deep dive failed: ${scrapeError instanceof Error ? scrapeError.message : String(scrapeError)})`;
|
|
106
|
+
}
|
|
107
|
+
combinedResults += `\n--- END DEEP DIVE ---\n`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
combinedResults += `\n--- END DATA ---\n\n`;
|
|
111
|
+
// Add analysis framework instructions
|
|
112
|
+
combinedResults += `INSTRUCTIONS: Act as a World-Class Football Analysis Panel. Provide a deep, non-obvious analysis using this framework:\n\n`;
|
|
113
|
+
combinedResults += `1. 📊 THE DATA SCIENTIST (Quantitative):\n - Analyze xG trends & Possession stats.\n - Assess Home/Away variance.\n\n`;
|
|
114
|
+
combinedResults += `2. 🧠 THE TACTICAL SCOUT (Qualitative):\n - Stylistic Matchup (Press vs Block).\n - Key Battles.\n\n`;
|
|
115
|
+
combinedResults += `3. 🚑 THE PHYSIO (Physical Condition):\n - FATIGUE CHECK: Days rest? Travel distance?\n - Squad Depth: Who has the better bench?\n\n`;
|
|
116
|
+
combinedResults += `4. 🎯 SET PIECE ANALYST:\n - Corners/Free Kicks: Strong vs Weak?\n - Who is most likely to score from a header?\n\n`;
|
|
117
|
+
combinedResults += `5. 💎 THE INSIDER (External Factors):\n - Market Movements (Odds dropping?).\n - Referee & Weather impact.\n\n`;
|
|
118
|
+
combinedResults += `6. 🕵️ THE SKEPTIC & SCENARIOS:\n - Why might the favorite LOSE?\n - Game Script: "If Team A scores first..."\n\n`;
|
|
119
|
+
combinedResults += `🏆 FINAL VERDICT:\n - Asian Handicap Leans\n - Goal Line (Over/Under)\n - The "Value Pick"`;
|
|
120
|
+
return combinedResults;
|
|
121
|
+
}
|
|
122
|
+
// ============= Tool Registration =============
|
|
123
|
+
export function registerMatchTools(server) {
|
|
124
|
+
/**
|
|
125
|
+
* Tool 1: analyze_football_match_v2
|
|
126
|
+
* Comprehensive football match analysis with API + Web search fallback
|
|
127
|
+
*/
|
|
128
|
+
server.tool('analyze_football_match_v2', `Comprehensive football match analysis.
|
|
129
|
+
Uses API providers when available, falls back to web search.
|
|
130
|
+
Gathers: Head-to-Head, Recent Form, Team News, Advanced Metrics (xG),
|
|
131
|
+
Market Intelligence, Referee stats, Weather, Fatigue analysis, Set Pieces.`, {
|
|
132
|
+
homeTeam: z.string().describe('Name of the home team'),
|
|
133
|
+
awayTeam: z.string().describe('Name of the away team'),
|
|
134
|
+
league: z.string().optional().describe('League name (optional, helps with accuracy)'),
|
|
135
|
+
context: z.string().optional().describe('Specific angle to focus on (e.g., "betting", "tactical", "injury")'),
|
|
136
|
+
useApi: z.boolean().optional().default(true).describe('Try to use direct API first if available'),
|
|
137
|
+
}, async ({ homeTeam, awayTeam, league, context, useApi }) => {
|
|
138
|
+
const errors = [];
|
|
139
|
+
let analysisResult = null;
|
|
140
|
+
// Try API providers first if requested
|
|
141
|
+
if (useApi) {
|
|
142
|
+
try {
|
|
143
|
+
const api = createAPIProvider();
|
|
144
|
+
const apiStatus = api.getStatus();
|
|
145
|
+
if (apiStatus.available) {
|
|
146
|
+
// Could try to fetch match data from API here
|
|
147
|
+
// For now, we'll use the search-based analysis as fallback
|
|
148
|
+
analysisResult = await performMatchAnalysis(homeTeam, awayTeam, league, context);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
errors.push('API providers not configured');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
errors.push(`API error: ${error instanceof Error ? error.message : String(error)}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Fallback to web search analysis
|
|
159
|
+
if (!analysisResult) {
|
|
160
|
+
analysisResult = await performMatchAnalysis(homeTeam, awayTeam, league, context);
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
content: [{ type: 'text', text: analysisResult }],
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
/**
|
|
167
|
+
* Tool 2: get_live_scores
|
|
168
|
+
* Get live football scores for a league or team
|
|
169
|
+
*/
|
|
170
|
+
server.tool('get_live_scores', `Get live football scores.
|
|
171
|
+
Supports filtering by league or team.
|
|
172
|
+
Data cached for 1 minute.`, {
|
|
173
|
+
league: z.string().optional().describe('Filter by league name (e.g., "Premier League")'),
|
|
174
|
+
team: z.string().optional().describe('Filter by team name'),
|
|
175
|
+
limit: z.number().optional().default(20).describe('Max matches to return'),
|
|
176
|
+
}, async ({ league, team, limit }) => {
|
|
177
|
+
const cache = getGlobalCache();
|
|
178
|
+
const cacheKey = CacheService.generateKey('live_scores', league || 'all', team || 'all', limit.toString());
|
|
179
|
+
// Check cache first
|
|
180
|
+
const cached = cache.get(cacheKey);
|
|
181
|
+
if (cached) {
|
|
182
|
+
return {
|
|
183
|
+
content: [{ type: 'text', text: cached + '\n\n*(cached data)*' }],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const errors = [];
|
|
187
|
+
let matches = [];
|
|
188
|
+
// Try API providers first
|
|
189
|
+
try {
|
|
190
|
+
const api = createAPIProvider();
|
|
191
|
+
const leagueId = league ? getLeagueId(league) : undefined;
|
|
192
|
+
const result = await api.getLiveMatches(leagueId?.toString());
|
|
193
|
+
if (result.success && result.data) {
|
|
194
|
+
matches = result.data;
|
|
195
|
+
// Filter by team if specified
|
|
196
|
+
if (team) {
|
|
197
|
+
matches = matches.filter((m) => m.homeTeam.name.toLowerCase().includes(team.toLowerCase()) ||
|
|
198
|
+
m.awayTeam.name.toLowerCase().includes(team.toLowerCase()));
|
|
199
|
+
}
|
|
200
|
+
// Limit results
|
|
201
|
+
matches = matches.slice(0, limit);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
errors.push(result.error || 'Unknown API error');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
209
|
+
}
|
|
210
|
+
// If no matches from API, try web search as fallback
|
|
211
|
+
if (matches.length === 0) {
|
|
212
|
+
try {
|
|
213
|
+
const searchQuery = league
|
|
214
|
+
? `${league} live scores today${getDateContext()}`
|
|
215
|
+
: `football live scores today${getDateContext()}`;
|
|
216
|
+
const searchProvider = getSearchProvider();
|
|
217
|
+
const searchResults = await searchProvider.search(searchQuery, undefined, 5);
|
|
218
|
+
// Format search results as fallback
|
|
219
|
+
const fallbackText = searchResults.map(r => `- [${r.title}](${r.url}): ${r.snippet}`).join('\n');
|
|
220
|
+
return {
|
|
221
|
+
content: [{
|
|
222
|
+
type: 'text',
|
|
223
|
+
text: `## Live Scores (via Search)\n\n${fallbackText}\n\n*API providers not available. Click links for live scores.*`
|
|
224
|
+
}],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
catch (searchError) {
|
|
228
|
+
errors.push(`Search fallback failed: ${searchError instanceof Error ? searchError.message : String(searchError)}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Format results
|
|
232
|
+
let output = `## ⚽ Live Scores`;
|
|
233
|
+
if (league)
|
|
234
|
+
output += ` - ${league}`;
|
|
235
|
+
if (team)
|
|
236
|
+
output += ` (${team})`;
|
|
237
|
+
output += `\n\n`;
|
|
238
|
+
if (matches.length > 0) {
|
|
239
|
+
output += formatMatchesTable(matches, 'Live Matches');
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
output += 'No live matches found.\n';
|
|
243
|
+
if (errors.length > 0) {
|
|
244
|
+
output += `\nErrors: ${errors.join(', ')}`;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Cache for 1 minute
|
|
248
|
+
cache.set(cacheKey, output, CACHE_CONFIG.TTL.LIVE_SCORES);
|
|
249
|
+
return {
|
|
250
|
+
content: [{ type: 'text', text: output }],
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
/**
|
|
254
|
+
* Tool 3: get_match_details
|
|
255
|
+
* Get detailed information about a specific match
|
|
256
|
+
*/
|
|
257
|
+
server.tool('get_match_details', `Get detailed match information by ID.
|
|
258
|
+
Requires API provider with match ID.`, {
|
|
259
|
+
matchId: z.string().describe('Match ID (from API)'),
|
|
260
|
+
provider: z.enum(['api-football', 'football-data', 'sports-db']).optional().describe('Preferred API provider'),
|
|
261
|
+
}, async ({ matchId, provider }) => {
|
|
262
|
+
try {
|
|
263
|
+
const api = createAPIProvider();
|
|
264
|
+
const result = await api.getMatch(matchId);
|
|
265
|
+
if (result.success && result.data) {
|
|
266
|
+
const match = result.data;
|
|
267
|
+
let output = `## Match Details\n\n`;
|
|
268
|
+
output += `**${match.homeTeam.name}** vs **${match.awayTeam.name}**\n`;
|
|
269
|
+
output += `**Competition:** ${match.league.name}\n`;
|
|
270
|
+
output += `**Date:** ${match.date.toLocaleString()}\n`;
|
|
271
|
+
output += `**Status:** ${formatMatchStatus(match.status)}\n`;
|
|
272
|
+
if (match.score) {
|
|
273
|
+
output += `**Score:** ${formatScore(match)}\n`;
|
|
274
|
+
}
|
|
275
|
+
if (match.venue) {
|
|
276
|
+
output += `**Venue:** ${match.venue}\n`;
|
|
277
|
+
}
|
|
278
|
+
if (match.referee) {
|
|
279
|
+
output += `**Referee:** ${match.referee.name}\n`;
|
|
280
|
+
}
|
|
281
|
+
if (match.status === 'live' && match.minute) {
|
|
282
|
+
output += `**Minute:** ${match.minute}'\n`;
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
content: [{ type: 'text', text: output }],
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
return {
|
|
290
|
+
content: [{
|
|
291
|
+
type: 'text',
|
|
292
|
+
text: `Error: ${result.error || 'Failed to fetch match details'}`
|
|
293
|
+
}],
|
|
294
|
+
isError: true,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
return {
|
|
300
|
+
content: [{
|
|
301
|
+
type: 'text',
|
|
302
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
303
|
+
}],
|
|
304
|
+
isError: true,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
// ============= Helper Functions =============
|
|
310
|
+
/**
|
|
311
|
+
* Get league ID from league name
|
|
312
|
+
*/
|
|
313
|
+
function getLeagueId(leagueName) {
|
|
314
|
+
const normalized = leagueName.toLowerCase();
|
|
315
|
+
for (const [key, value] of Object.entries(LEAGUES)) {
|
|
316
|
+
if (value.name.toLowerCase().includes(normalized) ||
|
|
317
|
+
normalized.includes(value.name.toLowerCase()) ||
|
|
318
|
+
value.code?.toLowerCase() === normalized.replace(' ', '')) {
|
|
319
|
+
return value.id;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPORTS MODULE TOOLS - Player Tools
|
|
3
|
+
* Tools for player analysis
|
|
4
|
+
*/
|
|
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 { formatPlayerStats } from '../utils/formatter.js';
|
|
10
|
+
import { CACHE_CONFIG } from '../core/constants.js';
|
|
11
|
+
// ============= Tool Registration =============
|
|
12
|
+
export function registerPlayerTools(server) {
|
|
13
|
+
/**
|
|
14
|
+
* Tool 1: player_analysis
|
|
15
|
+
* Detailed analysis of a specific player
|
|
16
|
+
*/
|
|
17
|
+
server.tool('player_analysis', `Get detailed analysis of a football player.
|
|
18
|
+
Includes: stats, form, injury status, transfer value, market value.`, {
|
|
19
|
+
player: z.string().describe('Player name'),
|
|
20
|
+
include: z.array(z.enum(['stats', 'form', 'injury', 'transfer', 'market-value']))
|
|
21
|
+
.optional().default(['stats', 'form'])
|
|
22
|
+
.describe('Information to include'),
|
|
23
|
+
}, async ({ player, include }) => {
|
|
24
|
+
const cache = getGlobalCache();
|
|
25
|
+
const includeArray = include || ['stats', 'form'];
|
|
26
|
+
const cacheKey = CacheService.generateKey('player', player, includeArray.join(','));
|
|
27
|
+
const cached = cache.get(cacheKey);
|
|
28
|
+
if (cached) {
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: 'text', text: cached + '\n\n*(cached data)*' }],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
let output = '';
|
|
34
|
+
const errors = [];
|
|
35
|
+
// Try API first
|
|
36
|
+
try {
|
|
37
|
+
const api = createAPIProvider();
|
|
38
|
+
const searchResult = await api.searchPlayers(player);
|
|
39
|
+
if (searchResult.success && searchResult.data && searchResult.data.length > 0) {
|
|
40
|
+
const playerData = searchResult.data[0];
|
|
41
|
+
output = `## 👤 ${playerData.name}\n\n`;
|
|
42
|
+
output += `**Age:** ${playerData.age || 'N/A'}\n`;
|
|
43
|
+
output += `**Nationality:** ${playerData.nationality}\n`;
|
|
44
|
+
output += `**Position:** ${playerData.position}\n`;
|
|
45
|
+
if (playerData.number)
|
|
46
|
+
output += `**Shirt Number:** ${playerData.number}\n`;
|
|
47
|
+
if (playerData.team)
|
|
48
|
+
output += `**Team:** ${playerData.team.name}\n`;
|
|
49
|
+
if (playerData.photo)
|
|
50
|
+
output += `**Photo:** ${playerData.photo}\n`;
|
|
51
|
+
if (includeArray.includes('stats') && playerData.stats) {
|
|
52
|
+
output += `\n### Season Statistics\n`;
|
|
53
|
+
output += formatPlayerStats(playerData.stats, playerData.name);
|
|
54
|
+
}
|
|
55
|
+
// Cache for 1 hour
|
|
56
|
+
cache.set(cacheKey, output, CACHE_CONFIG.TTL.PLAYER_STATS);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
errors.push('Player not found via API');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
64
|
+
}
|
|
65
|
+
// Fallback to web search
|
|
66
|
+
if (!output) {
|
|
67
|
+
try {
|
|
68
|
+
const searchQuery = `${player} football player stats profile`;
|
|
69
|
+
const searchProvider = getSearchProvider();
|
|
70
|
+
const searchResults = await searchProvider.search(searchQuery, undefined, 3);
|
|
71
|
+
const fallbackText = searchResults.map(r => `- [${r.title}](${r.url}): ${r.snippet}`).join('\n');
|
|
72
|
+
output = `## 👤 ${player}\n\n`;
|
|
73
|
+
output += `*API data not available. Click links for player info:*\n\n${fallbackText}`;
|
|
74
|
+
}
|
|
75
|
+
catch (searchError) {
|
|
76
|
+
output = `Error: ${errors.join(', ')}`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: 'text', text: output }],
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
/**
|
|
84
|
+
* Tool 2: tactical_breakdown
|
|
85
|
+
* Analyze tactical matchups between teams
|
|
86
|
+
*/
|
|
87
|
+
server.tool('tactical_breakdown', `Analyze tactical matchups between two teams.
|
|
88
|
+
Includes: formations, playing styles, key battles, strengths/weaknesses.`, {
|
|
89
|
+
homeTeam: z.string().describe('Home team name'),
|
|
90
|
+
awayTeam: z.string().describe('Away team name'),
|
|
91
|
+
focus: z.enum(['formation', 'pressing', 'set-pieces', 'all']).optional().default('all')
|
|
92
|
+
.describe('Tactical focus area'),
|
|
93
|
+
}, async ({ homeTeam, awayTeam, focus }) => {
|
|
94
|
+
const searchQuery = `${homeTeam} ${awayTeam} tactical analysis formation pressing`;
|
|
95
|
+
let output = `## 🧠 Tactical Breakdown: ${homeTeam} vs ${awayTeam}\n\n`;
|
|
96
|
+
try {
|
|
97
|
+
const searchProvider = getSearchProvider();
|
|
98
|
+
const searchResults = await searchProvider.search(searchQuery, undefined, 5);
|
|
99
|
+
output += `### Tactical Analysis Sources\n\n`;
|
|
100
|
+
const items = searchResults.map(r => `- [${r.title}](${r.url}): ${r.snippet}`).join('\n');
|
|
101
|
+
output += items;
|
|
102
|
+
output += `\n\n### Key Tactical Areas\n\n`;
|
|
103
|
+
output += `| Area | Home Team | Away Team |\n`;
|
|
104
|
+
output += `|------|-----------|----------|\n`;
|
|
105
|
+
output += `| Formation | Analyzing... | Analyzing... |\n`;
|
|
106
|
+
output += `| Playing Style | Analyzing... | Analyzing... |\n`;
|
|
107
|
+
output += `| Pressing | Analyzing... | Analyzing... |\n`;
|
|
108
|
+
output += `| Set Pieces | Analyzing... | Analyzing... |\n`;
|
|
109
|
+
output += `\n*Note: Full tactical analysis requires detailed match data API.*`;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
output += `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: 'text', text: output }],
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* Tool 3: referee_profile
|
|
120
|
+
* Get referee statistics and tendencies
|
|
121
|
+
*/
|
|
122
|
+
server.tool('referee_profile', `Get referee statistics and tendencies.
|
|
123
|
+
Includes: cards per game, home/away bias, penalties awarded.`, {
|
|
124
|
+
referee: z.string().describe('Referee name'),
|
|
125
|
+
league: z.string().optional().describe('Filter by league'),
|
|
126
|
+
}, async ({ referee, league }) => {
|
|
127
|
+
const searchQuery = `${referee} referee stats cards per game${league ? ` ${league}` : ''}`;
|
|
128
|
+
let output = `## 🎨 Referee Profile: ${referee}\n\n`;
|
|
129
|
+
try {
|
|
130
|
+
const searchProvider = getSearchProvider();
|
|
131
|
+
const searchResults = await searchProvider.search(searchQuery, undefined, 3);
|
|
132
|
+
output += `### Statistics\n\n`;
|
|
133
|
+
output += `| Statistic | Value |\n`;
|
|
134
|
+
output += `|-----------|-------|\n`;
|
|
135
|
+
output += `| Yellow Cards/Game | Searching... |\n`;
|
|
136
|
+
output += `| Red Cards/Game | Searching... |\n`;
|
|
137
|
+
output += `| Penalties/Game | Searching... |\n`;
|
|
138
|
+
output += `| Home Win Rate | Searching... |\n`;
|
|
139
|
+
output += `| Away Win Rate | Searching... |\n`;
|
|
140
|
+
output += `\n### Data Sources\n\n`;
|
|
141
|
+
const items = searchResults.map(r => `- [${r.title}](${r.url}): ${r.snippet}`).join('\n');
|
|
142
|
+
output += items;
|
|
143
|
+
output += `\n\n*Note: Detailed referee statistics require dedicated API integration.*`;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
output += `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: 'text', text: output }],
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
}
|