@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,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPORTS MODULE TOOLS - Betting Tools
|
|
3
|
+
* Tools for betting intelligence and odds comparison
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { getSearchProvider } from '../providers/search.js';
|
|
7
|
+
import { calculateValue, calculateKelly, calculateRecommendedStake, oddsToProbability } from '../utils/calculator.js';
|
|
8
|
+
import { formatOdds } from '../utils/formatter.js';
|
|
9
|
+
import { BETTING } from '../core/constants.js';
|
|
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
|
+
/**
|
|
25
|
+
* Search for odds by match
|
|
26
|
+
*/
|
|
27
|
+
async function searchOdds(homeTeam, awayTeam) {
|
|
28
|
+
const searchQuery = `${homeTeam} vs ${awayTeam} odds betting`;
|
|
29
|
+
const searchProvider = getSearchProvider();
|
|
30
|
+
try {
|
|
31
|
+
const results = await searchProvider.search(searchQuery, undefined, 5);
|
|
32
|
+
let output = `## 💰 Odds: ${homeTeam} vs ${awayTeam}\n\n`;
|
|
33
|
+
output += `### Available Odds\n\n`;
|
|
34
|
+
const items = results.map(r => `- [${r.title}](${r.url}): ${r.snippet}`).join('\n');
|
|
35
|
+
output += items;
|
|
36
|
+
output += `\n\n*Click links to view current odds from bookmakers.*`;
|
|
37
|
+
return output;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return `Error searching for odds: ${error instanceof Error ? error.message : String(error)}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// ============= Tool Registration =============
|
|
44
|
+
export function registerBettingTools(server) {
|
|
45
|
+
/**
|
|
46
|
+
* Tool 1: odds_comparison
|
|
47
|
+
* Compare betting odds across multiple bookmakers
|
|
48
|
+
*/
|
|
49
|
+
server.tool('odds_comparison', `Compare betting odds across multiple bookmakers.
|
|
50
|
+
Shows: Home Win, Draw, Away Win, Asian Handicap, Over/Under.`, {
|
|
51
|
+
matchId: z.string().optional().describe('API match ID if known'),
|
|
52
|
+
homeTeam: z.string().optional().describe('Home team name (for search fallback)'),
|
|
53
|
+
awayTeam: z.string().optional().describe('Away team name (for search fallback)'),
|
|
54
|
+
}, async ({ matchId, homeTeam, awayTeam }) => {
|
|
55
|
+
const errors = [];
|
|
56
|
+
let output = '';
|
|
57
|
+
if (homeTeam && awayTeam) {
|
|
58
|
+
output = await searchOdds(homeTeam, awayTeam);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
errors.push('Either matchId OR both homeTeam and awayTeam must be provided');
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: 'text', text: output || `Error: ${errors.join(', ')}` }],
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* Tool 2: value_bet_calculator
|
|
69
|
+
* Calculate betting value using Kelly Criterion
|
|
70
|
+
*/
|
|
71
|
+
server.tool('value_bet_calculator', `Calculate betting value and recommended stake.
|
|
72
|
+
Shows: Fair odds, market odds, value %, confidence, Kelly stake.`, {
|
|
73
|
+
odds: z.number().describe('Decimal odds offered by bookmaker'),
|
|
74
|
+
probability: z.number().describe('Your estimated probability (0-1, e.g., 0.60 = 60%)'),
|
|
75
|
+
bankroll: z.number().optional().describe('Total bankroll (default: 1000)'),
|
|
76
|
+
kellyVariant: z.enum(['full', 'half', 'quarter']).optional().default('half')
|
|
77
|
+
.describe('Kelly Criterion variant'),
|
|
78
|
+
}, async ({ odds, probability, bankroll, kellyVariant }) => {
|
|
79
|
+
if (odds <= 1) {
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: 'text', text: 'Error: Odds must be greater than 1.00' }],
|
|
82
|
+
isError: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (probability < 0 || probability > 1) {
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: 'text', text: 'Error: Probability must be between 0 and 1' }],
|
|
88
|
+
isError: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const value = calculateValue(odds, probability);
|
|
92
|
+
const kelly = calculateKelly(odds, probability, kellyVariant);
|
|
93
|
+
const recommendedStake = calculateRecommendedStake(odds, probability, bankroll || BETTING.DEFAULT_BANKROLL, kellyVariant);
|
|
94
|
+
const fairOdds = 1 / probability;
|
|
95
|
+
const impliedProb = oddsToProbability(odds);
|
|
96
|
+
let output = `## 💰 Value Bet Analysis\n\n`;
|
|
97
|
+
output += `| Metric | Value |\n`;
|
|
98
|
+
output += `|--------|-------|\n`;
|
|
99
|
+
output += `| Market Odds | ${formatOdds(odds)} |\n`;
|
|
100
|
+
output += `| Fair Odds | ${fairOdds.toFixed(2)} |\n`;
|
|
101
|
+
output += `| Your Probability | ${(probability * 100).toFixed(1)}% |\n`;
|
|
102
|
+
output += `| Implied Probability | ${(impliedProb * 100).toFixed(1)}% |\n`;
|
|
103
|
+
output += `| **Value** | **${value > 0 ? '+' : ''}${(value * 100).toFixed(1)}%** |\n`;
|
|
104
|
+
output += `\n### Kelly Criterion (${kellyVariant})\n\n`;
|
|
105
|
+
output += `| Metric | Value |\n`;
|
|
106
|
+
output += `|--------|-------|\n`;
|
|
107
|
+
output += `| Kelly Fraction | ${(kelly * 100).toFixed(2)}% |\n`;
|
|
108
|
+
output += `| Recommended Stake | ${recommendedStake.toFixed(2)} units |\n`;
|
|
109
|
+
output += `| Based on Bankroll | ${bankroll || BETTING.DEFAULT_BANKROLL} units |\n`;
|
|
110
|
+
// Verdict
|
|
111
|
+
output += `\n### Verdict\n\n`;
|
|
112
|
+
if (value < -0.05) {
|
|
113
|
+
output += `❌ **Negative Value** - This bet is not worth taking.\n`;
|
|
114
|
+
}
|
|
115
|
+
else if (value < 0) {
|
|
116
|
+
output += `⚠️ **Low Value** - Minimal edge, consider skipping.\n`;
|
|
117
|
+
}
|
|
118
|
+
else if (value < BETTING.HIGH_VALUE_THRESHOLD) {
|
|
119
|
+
output += `✅ **Value Bet** - Positive edge detected.\n`;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
output += `🔥 **HIGH VALUE** - Strong value opportunity!\n`;
|
|
123
|
+
}
|
|
124
|
+
// Risk warning
|
|
125
|
+
if (kelly > 0.05) {
|
|
126
|
+
output += `\n⚠️ **Warning:** High stake recommendation. Consider using half or quarter Kelly.\n`;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
content: [{ type: 'text', text: output }],
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
/**
|
|
133
|
+
* Tool 3: odds_converter
|
|
134
|
+
* Convert between decimal, fractional, and American odds
|
|
135
|
+
*/
|
|
136
|
+
server.tool('odds_converter', `Convert between decimal, fractional, and American odds formats.`, {
|
|
137
|
+
odds: z.number().describe('Odds value to convert'),
|
|
138
|
+
fromFormat: z.enum(['decimal', 'fractional', 'american'])
|
|
139
|
+
.describe('Input format'),
|
|
140
|
+
toFormat: z.enum(['decimal', 'fractional', 'american', 'all'])
|
|
141
|
+
.optional().default('all')
|
|
142
|
+
.describe('Output format'),
|
|
143
|
+
}, async ({ odds, fromFormat, toFormat }) => {
|
|
144
|
+
// Fallback for Zod default value issue
|
|
145
|
+
const targetFormat = toFormat || 'all';
|
|
146
|
+
let decimalOdds;
|
|
147
|
+
// Convert input to decimal
|
|
148
|
+
switch (fromFormat) {
|
|
149
|
+
case 'decimal':
|
|
150
|
+
decimalOdds = odds;
|
|
151
|
+
break;
|
|
152
|
+
case 'american':
|
|
153
|
+
if (odds > 0) {
|
|
154
|
+
decimalOdds = (odds / 100) + 1;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
decimalOdds = (100 / Math.abs(odds)) + 1;
|
|
158
|
+
}
|
|
159
|
+
break;
|
|
160
|
+
case 'fractional':
|
|
161
|
+
// Parse fractional odds like "5/2" or "5-2"
|
|
162
|
+
const oddsStr = odds.toString();
|
|
163
|
+
const parts = oddsStr.split(/[-/]/);
|
|
164
|
+
if (parts.length === 2) {
|
|
165
|
+
decimalOdds = (parseFloat(parts[0]) / parseFloat(parts[1])) + 1;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
decimalOdds = odds;
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
if (isNaN(decimalOdds) || decimalOdds < 1) {
|
|
173
|
+
return {
|
|
174
|
+
content: [{ type: 'text', text: 'Error: Invalid odds value' }],
|
|
175
|
+
isError: true,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
let output = `## 📊 Odds Conversion\n\n`;
|
|
179
|
+
output += `**Input:** ${odds} (${fromFormat})\n\n`;
|
|
180
|
+
if (targetFormat === 'all') {
|
|
181
|
+
const american = decimalOdds >= 2
|
|
182
|
+
? Math.round((decimalOdds - 1) * 100)
|
|
183
|
+
: Math.round(-100 / (decimalOdds - 1));
|
|
184
|
+
const fractional = decimalOdds - 1;
|
|
185
|
+
const impliedProb = oddsToProbability(decimalOdds);
|
|
186
|
+
output += `| Format | Odds |\n`;
|
|
187
|
+
output += `|--------|------|\n`;
|
|
188
|
+
output += `| Decimal | ${decimalOdds.toFixed(2)} |\n`;
|
|
189
|
+
output += `| American | ${american > 0 ? '+' : ''}${american} |\n`;
|
|
190
|
+
output += `| Fractional | ${fractional.toFixed(2)}/1 |\n`;
|
|
191
|
+
output += `| Implied Prob | ${(impliedProb * 100).toFixed(1)}% |\n`;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
// Convert to specific format
|
|
195
|
+
switch (toFormat) {
|
|
196
|
+
case 'decimal':
|
|
197
|
+
output += `**Result:** ${decimalOdds.toFixed(2)}`;
|
|
198
|
+
break;
|
|
199
|
+
case 'american':
|
|
200
|
+
const american = decimalOdds >= 2
|
|
201
|
+
? Math.round((decimalOdds - 1) * 100)
|
|
202
|
+
: Math.round(-100 / (decimalOdds - 1));
|
|
203
|
+
output += `**Result:** ${american > 0 ? '+' : ''}${american}`;
|
|
204
|
+
break;
|
|
205
|
+
case 'fractional':
|
|
206
|
+
const fractional = decimalOdds - 1;
|
|
207
|
+
output += `**Result:** ${fractional.toFixed(2)}/1`;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
content: [{ type: 'text', text: output }],
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
/**
|
|
216
|
+
* Tool 4: probability_calculator
|
|
217
|
+
* Calculate probabilities and implied odds
|
|
218
|
+
*/
|
|
219
|
+
server.tool('probability_calculator', `Calculate implied probability from odds and vice versa.
|
|
220
|
+
Useful for converting between odds and probabilities.`, {
|
|
221
|
+
odds: z.number().optional().describe('Decimal odds to convert to probability'),
|
|
222
|
+
probability: z.number().optional().describe('Probability (0-1) to convert to odds'),
|
|
223
|
+
}, async ({ odds, probability }) => {
|
|
224
|
+
let output = `## 📊 Probability Calculator\n\n`;
|
|
225
|
+
if (odds) {
|
|
226
|
+
const impliedProb = oddsToProbability(odds);
|
|
227
|
+
const american = odds >= 2
|
|
228
|
+
? Math.round((odds - 1) * 100)
|
|
229
|
+
: Math.round(-100 / (odds - 1));
|
|
230
|
+
output += `### From Odds: ${formatOdds(odds)}\n\n`;
|
|
231
|
+
output += `- **Implied Probability:** ${(impliedProb * 100).toFixed(1)}%\n`;
|
|
232
|
+
output += `- **American Odds:** ${american > 0 ? '+' : ''}${american}\n`;
|
|
233
|
+
output += `- **Break-even Rate:** ${(impliedProb * 100).toFixed(1)}%\n`;
|
|
234
|
+
}
|
|
235
|
+
if (probability) {
|
|
236
|
+
const fairOdds = 1 / probability;
|
|
237
|
+
const american = fairOdds >= 2
|
|
238
|
+
? Math.round((fairOdds - 1) * 100)
|
|
239
|
+
: Math.round(-100 / (fairOdds - 1));
|
|
240
|
+
output += `\n### From Probability: ${(probability * 100).toFixed(1)}%\n\n`;
|
|
241
|
+
output += `- **Fair Odds:** ${fairOdds.toFixed(2)}\n`;
|
|
242
|
+
output += `- **American Odds:** ${american > 0 ? '+' : ''}${american}\n`;
|
|
243
|
+
}
|
|
244
|
+
if (!odds && !probability) {
|
|
245
|
+
output += `Please provide either odds or probability.`;
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
content: [{ type: 'text', text: output }],
|
|
249
|
+
};
|
|
250
|
+
});
|
|
251
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPORTS MODULE TOOLS - League Tools
|
|
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
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
// Placeholder - Tools will be implemented in Phase 2
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPORTS MODULE TOOLS - Live Tools
|
|
3
|
+
* Tools for live match data and alerts
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
+
/**
|
|
7
|
+
* Register live and alert tools
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerLiveTools(server: McpServer): void;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPORTS MODULE TOOLS - Live Tools
|
|
3
|
+
* Tools for live match data and alerts
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { getSearchProvider } from '../providers/search.js';
|
|
7
|
+
// In-memory watchlist storage
|
|
8
|
+
const watchlist = [];
|
|
9
|
+
/**
|
|
10
|
+
* Register live and alert tools
|
|
11
|
+
*/
|
|
12
|
+
export function registerLiveTools(server) {
|
|
13
|
+
/**
|
|
14
|
+
* Tool 1: watchlist_add
|
|
15
|
+
* Add an item to the watchlist for monitoring
|
|
16
|
+
*/
|
|
17
|
+
server.tool('watchlist_add', `Add an item to the sports watchlist.
|
|
18
|
+
Monitors: matches, odds changes, team news, injuries.
|
|
19
|
+
|
|
20
|
+
Can be called with:
|
|
21
|
+
1. type + target + condition (simple mode)
|
|
22
|
+
2. matchId + homeTeam + awayTeam + alertCondition (match mode)`, {
|
|
23
|
+
// Simple mode
|
|
24
|
+
type: z.enum(['match', 'odds', 'news', 'injury']).optional().describe('Type of alert'),
|
|
25
|
+
target: z.string().optional().describe('Target to monitor (team name, match ID, etc.)'),
|
|
26
|
+
condition: z.string().optional().describe('Alert condition (e.g., "odds < 2.00", "injury news")'),
|
|
27
|
+
// Match mode
|
|
28
|
+
matchId: z.string().optional().describe('Match ID for match monitoring'),
|
|
29
|
+
homeTeam: z.string().optional().describe('Home team name'),
|
|
30
|
+
awayTeam: z.string().optional().describe('Away team name'),
|
|
31
|
+
alertCondition: z.string().optional().describe('Alert condition for match'),
|
|
32
|
+
}, async ({ type, target, condition, matchId, homeTeam, awayTeam, alertCondition }) => {
|
|
33
|
+
const id = `watch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
34
|
+
// Handle match mode (matchId, homeTeam, awayTeam provided)
|
|
35
|
+
let itemType = type;
|
|
36
|
+
let itemTarget = target;
|
|
37
|
+
let itemCondition = condition;
|
|
38
|
+
if (matchId && (homeTeam || awayTeam)) {
|
|
39
|
+
// Match mode: derive type/target from match parameters
|
|
40
|
+
itemType = 'match';
|
|
41
|
+
itemTarget = matchId;
|
|
42
|
+
if (!itemCondition) {
|
|
43
|
+
itemCondition = alertCondition || 'monitor';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Simple mode: use provided values
|
|
48
|
+
if (!itemType)
|
|
49
|
+
itemType = 'match';
|
|
50
|
+
if (!itemTarget)
|
|
51
|
+
itemTarget = 'unknown';
|
|
52
|
+
if (!itemCondition)
|
|
53
|
+
itemCondition = 'monitor';
|
|
54
|
+
}
|
|
55
|
+
const item = {
|
|
56
|
+
id,
|
|
57
|
+
type: itemType,
|
|
58
|
+
target: itemTarget,
|
|
59
|
+
condition: itemCondition,
|
|
60
|
+
createdAt: new Date().toISOString(),
|
|
61
|
+
triggered: false,
|
|
62
|
+
};
|
|
63
|
+
watchlist.push(item);
|
|
64
|
+
let output = `## 📋 Added to Watchlist\n\n`;
|
|
65
|
+
output += `**ID:** ${id}\n`;
|
|
66
|
+
output += `**Type:** ${itemType}\n`;
|
|
67
|
+
output += `**Target:** ${itemTarget}\n`;
|
|
68
|
+
if (itemCondition)
|
|
69
|
+
output += `**Condition:** ${itemCondition}\n`;
|
|
70
|
+
if (homeTeam && awayTeam) {
|
|
71
|
+
output += `**Match:** ${homeTeam} vs ${awayTeam}\n`;
|
|
72
|
+
}
|
|
73
|
+
output += `**Created:** ${item.createdAt}\n`;
|
|
74
|
+
output += `\n\nTotal watchlist items: ${watchlist.length}`;
|
|
75
|
+
return {
|
|
76
|
+
content: [{ type: 'text', text: output }],
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* Tool 2: watchlist_list
|
|
81
|
+
* List all items in the watchlist
|
|
82
|
+
*/
|
|
83
|
+
server.tool('watchlist_list', `List all items in the sports watchlist.`, {}, async () => {
|
|
84
|
+
if (watchlist.length === 0) {
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: 'text', text: '## 📋 Watchlist\n\nNo items in watchlist.' }],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
let output = `## 📋 Watchlist (${watchlist.length} items)\n\n`;
|
|
90
|
+
for (const item of watchlist) {
|
|
91
|
+
const status = item.triggered ? '✅' : '👀';
|
|
92
|
+
output += `${status} **${item.id}**\n`;
|
|
93
|
+
output += `- Type: ${item.type}\n`;
|
|
94
|
+
output += `- Target: ${item.target}\n`;
|
|
95
|
+
if (item.condition)
|
|
96
|
+
output += `- Condition: ${item.condition}\n`;
|
|
97
|
+
output += `- Created: ${item.createdAt}\n\n`;
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
content: [{ type: 'text', text: output }],
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
/**
|
|
104
|
+
* Tool 3: watchlist_remove
|
|
105
|
+
* Remove an item from the watchlist
|
|
106
|
+
*/
|
|
107
|
+
server.tool('watchlist_remove', `Remove an item from the sports watchlist.`, {
|
|
108
|
+
id: z.string().describe('Watchlist item ID to remove'),
|
|
109
|
+
}, async ({ id }) => {
|
|
110
|
+
const index = watchlist.findIndex(item => item.id === id);
|
|
111
|
+
if (index === -1) {
|
|
112
|
+
return {
|
|
113
|
+
content: [{ type: 'text', text: `Error: Watchlist item "${id}" not found.` }],
|
|
114
|
+
isError: true,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const removed = watchlist.splice(index, 1)[0];
|
|
118
|
+
let output = `## 📋 Removed from Watchlist\n\n`;
|
|
119
|
+
output += `**ID:** ${removed.id}\n`;
|
|
120
|
+
output += `**Type:** ${removed.type}\n`;
|
|
121
|
+
output += `**Target:** ${removed.target}\n`;
|
|
122
|
+
output += `\n\nRemaining items: ${watchlist.length}`;
|
|
123
|
+
return {
|
|
124
|
+
content: [{ type: 'text', text: output }],
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
/**
|
|
128
|
+
* Tool 4: watchlist_clear
|
|
129
|
+
* Clear all items from the watchlist
|
|
130
|
+
*/
|
|
131
|
+
server.tool('watchlist_clear', `Clear all items from the sports watchlist.`, {}, async () => {
|
|
132
|
+
const count = watchlist.length;
|
|
133
|
+
watchlist.length = 0;
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: 'text', text: `## 📋 Watchlist Cleared\n\nRemoved ${count} item(s).` }],
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
/**
|
|
139
|
+
* Tool 5: check_alerts
|
|
140
|
+
* Check watchlist for triggered alerts
|
|
141
|
+
*/
|
|
142
|
+
server.tool('check_alerts', `Check watchlist for triggered alerts based on current data.`, {}, async () => {
|
|
143
|
+
let output = `## 🔔 Alert Check Results\n\n`;
|
|
144
|
+
if (watchlist.length === 0) {
|
|
145
|
+
output += 'No items in watchlist.';
|
|
146
|
+
return {
|
|
147
|
+
content: [{ type: 'text', text: output }],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const searchProvider = getSearchProvider();
|
|
151
|
+
let triggeredCount = 0;
|
|
152
|
+
for (const item of watchlist) {
|
|
153
|
+
let triggered = false;
|
|
154
|
+
let message = '';
|
|
155
|
+
// Check based on type
|
|
156
|
+
try {
|
|
157
|
+
if (item.type === 'news' || item.type === 'injury') {
|
|
158
|
+
const searchQuery = `${item.target} ${item.type === 'injury' ? 'injury news' : 'news'} today`;
|
|
159
|
+
const results = await searchProvider.search(searchQuery, undefined, 1);
|
|
160
|
+
if (results.length > 0) {
|
|
161
|
+
triggered = true;
|
|
162
|
+
message = `New ${item.type} found for ${item.target}`;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else if (item.type === 'odds') {
|
|
166
|
+
const searchQuery = `${item.target} odds`;
|
|
167
|
+
const results = await searchProvider.search(searchQuery, undefined, 1);
|
|
168
|
+
if (results.length > 0) {
|
|
169
|
+
// Would need to parse actual odds to check condition
|
|
170
|
+
triggered = true;
|
|
171
|
+
message = `Odds data available for ${item.target}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
// Continue checking other items
|
|
177
|
+
}
|
|
178
|
+
if (triggered) {
|
|
179
|
+
triggeredCount++;
|
|
180
|
+
output += `🔔 **${item.id}**: ${message}\n`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (triggeredCount === 0) {
|
|
184
|
+
output += 'No new alerts. All conditions normal.';
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
output += `\n\n**Total Alerts:** ${triggeredCount}`;
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
content: [{ type: 'text', text: output }],
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
/**
|
|
194
|
+
* Tool 6: transfer_news
|
|
195
|
+
* Get latest transfer rumors and confirmed deals
|
|
196
|
+
*/
|
|
197
|
+
server.tool('transfer_news', `Get latest transfer rumors and confirmed deals.`, {
|
|
198
|
+
team: z.string().optional().describe('Filter by team'),
|
|
199
|
+
player: z.string().optional().describe('Filter by player'),
|
|
200
|
+
league: z.string().optional().describe('Filter by league'),
|
|
201
|
+
}, async ({ team, player, league }) => {
|
|
202
|
+
let searchQuery = 'football transfer news rumors';
|
|
203
|
+
if (team)
|
|
204
|
+
searchQuery += ` ${team}`;
|
|
205
|
+
if (player)
|
|
206
|
+
searchQuery += ` ${player}`;
|
|
207
|
+
if (league)
|
|
208
|
+
searchQuery += ` ${league}`;
|
|
209
|
+
searchQuery += ` ${new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}`;
|
|
210
|
+
try {
|
|
211
|
+
const searchProvider = getSearchProvider();
|
|
212
|
+
const results = await searchProvider.search(searchQuery, undefined, 10);
|
|
213
|
+
let output = `## 💸 Transfer News\n\n`;
|
|
214
|
+
if (results.length > 0) {
|
|
215
|
+
const items = results.map(r => `- [${r.title}](${r.url}): ${r.snippet}`).join('\n\n');
|
|
216
|
+
output += items;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
output += 'No transfer news found.';
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
content: [{ type: 'text', text: output }],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
return {
|
|
227
|
+
content: [{
|
|
228
|
+
type: 'text',
|
|
229
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
230
|
+
}],
|
|
231
|
+
isError: true,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|