@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.
Files changed (35) hide show
  1. package/README.md.bak +197 -0
  2. package/dist/.gemini_graph_cache.json.bak +1641 -0
  3. package/dist/graph.d.ts +7 -0
  4. package/dist/graph.js +136 -113
  5. package/dist/intelligent-code.d.ts +8 -0
  6. package/dist/intelligent-code.js +152 -125
  7. package/dist/intelligent-code.test.d.ts +1 -0
  8. package/dist/intelligent-code.test.js +104 -0
  9. package/dist/lib/formatters.d.ts +9 -0
  10. package/dist/lib/formatters.js +119 -0
  11. package/dist/lib/validators.d.ts +45 -0
  12. package/dist/lib/validators.js +232 -0
  13. package/dist/lib.js +23 -265
  14. package/dist/tools/sports/core/base.d.ts +3 -2
  15. package/dist/tools/sports/core/base.js +12 -10
  16. package/dist/tools/sports/core/cache.d.ts +9 -0
  17. package/dist/tools/sports/core/cache.js +25 -3
  18. package/dist/tools/sports/core/types.d.ts +6 -2
  19. package/dist/tools/sports/providers/api.d.ts +4 -0
  20. package/dist/tools/sports/providers/api.js +110 -27
  21. package/dist/tools/sports/tools/betting.js +16 -16
  22. package/dist/tools/sports/tools/league.d.ts +2 -7
  23. package/dist/tools/sports/tools/league.js +198 -8
  24. package/dist/tools/sports/tools/live.js +80 -38
  25. package/dist/tools/sports/tools/match-calculations.d.ts +51 -0
  26. package/dist/tools/sports/tools/match-calculations.js +171 -0
  27. package/dist/tools/sports/tools/match-helpers.d.ts +21 -0
  28. package/dist/tools/sports/tools/match-helpers.js +57 -0
  29. package/dist/tools/sports/tools/match.js +227 -125
  30. package/dist/tools/sports.js +3 -3
  31. package/dist/utils.d.ts +111 -44
  32. package/dist/utils.js +510 -305
  33. package/dist/utils.test.js +3 -3
  34. package/package.json +1 -1
  35. 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 response = await this.callAPI(`/players?id=${playerId}&season=2024`);
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 response = await this.callAPI(`/players?search=${encodeURIComponent(query)}&season=2024`);
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.halftime ? {
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
- against: data.home.goals.against,
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
- against: data.away.goals.against,
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: matches[0]?.homeTeam.id === homeId ? matches[0].homeTeam : matches[0]?.awayTeam,
281
- awayTeam: matches[0]?.homeTeam.id === awayId ? matches[0].homeTeam : matches[0]?.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?.[0]) {
310
- const ahValue = ahBet.values[0];
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(ahValue.odd) || 2.0,
313
- away: parseFloat(ahValue.odd) || 2.0,
314
- line: parseFloat(ahValue.value) || 0,
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
- if (ouBet?.values?.[0]) {
320
- const ouValue = ouBet.values[0];
321
- const line = parseFloat(ouValue.value?.replace('Over ', '').replace('Under ', '')) || 2.5;
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(ouValue.odd) || 2.0,
325
- under: parseFloat(ouValue.odd) || 2.0,
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 response = await this.callAPI('/matches?status=IN_PLAY');
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
- against: data.homeGames.goalsAgainst,
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
- against: data.awayGames.goalsAgainst,
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
- decimalOdds = (parseFloat(parts[0]) / parseFloat(parts[1])) + 1;
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
- decimalOdds = odds;
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
- export {};
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
- export {};
12
- // Placeholder - Tools will be implemented in Phase 2
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
+ }