Skip to main content
Glama

NHL MCP Server

by dylangroos
server.ts152 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; const NHL_WEB_API_BASE = "https://api-web.nhle.com/v1"; const NHL_STATS_API_BASE = "https://api.nhle.com/stats/rest/en"; const USER_AGENT = "nhl-stats-app/1.0"; // Create server instance const server = new McpServer({ name: "nhl-api", version: "1.0.0", }); // Helper function for making NHL API requests async function makeNHLRequest<T>(url: string): Promise<T | null> { const headers = { "User-Agent": USER_AGENT, "Accept": "application/json", "Content-Type": "application/json" }; try { const response = await fetch(url, { headers }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return (await response.json()) as T; } catch (error) { console.error("Error making NHL request:", error); return null; } } // Types for NHL API responses interface TeamResponse { teamId?: number; name?: string; abbrev?: string; locationName?: string; teamName?: string; division?: { name: string; shortName: string; }; conference?: { name: string; shortName: string; }; roster?: PlayerResponse[]; record?: { wins: number; losses: number; otLosses: number; points: number; }; } interface PlayerResponse { playerId?: number; isActive?: boolean; currentTeamId?: number; currentTeamAbbrev?: string; fullTeamName?: { default: string; fr?: string; }; teamCommonName?: { default: string; }; teamPlaceNameWithPreposition?: { default: string; fr?: string; }; firstName?: { default: string; }; lastName?: { default: string; }; badges?: Array<{ logoUrl?: { default: string; fr?: string; }; title?: { default: string; fr?: string; }; }>; teamLogo?: string; sweaterNumber?: number; position?: string; headshot?: string; heroImage?: string; heightInInches?: number; heightInCentimeters?: number; weightInPounds?: number; weightInKilograms?: number; birthDate?: string; birthCity?: { default: string; }; birthStateProvince?: { default: string; }; birthCountry?: string; shootsCatches?: string; draftDetails?: { year?: number; teamAbbrev?: string; round?: number; pickInRound?: number; overallPick?: number; }; playerSlug?: string; featuredStats?: { season?: number; regularSeason?: { subSeason?: { assists?: number; gameWinningGoals?: number; gamesPlayed?: number; goals?: number; otGoals?: number; pim?: number; plusMinus?: number; points?: number; powerPlayGoals?: number; powerPlayPoints?: number; shootingPctg?: number; shorthandedGoals?: number; shorthandedPoints?: number; shots?: number; wins?: number; losses?: number; otLosses?: number; savePctg?: number; goalsAgainstAverage?: number; shutouts?: number; }; career?: { assists?: number; gameWinningGoals?: number; gamesPlayed?: number; goals?: number; otGoals?: number; pim?: number; plusMinus?: number; points?: number; powerPlayGoals?: number; powerPlayPoints?: number; shootingPctg?: number; shorthandedGoals?: number; shorthandedPoints?: number; shots?: number; }; }; }; careerTotals?: { regularSeason?: any; playoffs?: any; }; last5Games?: Array<any>; seasonTotals?: Array<any>; awards?: Array<any>; currentTeamRoster?: Array<any>; } interface StandingsResponse { wildCardIndicator?: boolean; standingsDateTimeUtc?: string; standings?: { conferenceAbbrev?: string; conferenceName?: string; divisionAbbrev?: string; divisionName?: string; teamName?: { default: string; }; teamCommonName?: { default: string; }; teamAbbrev?: { default: string; }; placeName?: { default: string; }; wins?: number; losses?: number; otLosses?: number; points?: number; goalsFor?: number; goalAgainst?: number; teamLogo?: string; }[]; } interface StandingsRecord { teamId?: number; teamName?: { default: string; }; teamAbbrev?: { default: string; }; gamesPlayed?: number; wins?: number; losses?: number; otLosses?: number; points?: number; goalsFor?: number; goalsAgainst?: number; homeRecord?: string; awayRecord?: string; streakCode?: string; streakCount?: number; } interface ScheduleResponse { gameWeek?: { date?: string; dayAbbrev?: string; numberOfGames?: number; games?: GameResponse[]; }[]; } interface GameResponse { id?: number; season?: string; gameType?: number; gameDate?: string; venue?: { default: string; }; startTimeUTC?: string; homeTeam?: { id: number; name: string; abbrev: string; score: number; }; awayTeam?: { id: number; name: string; abbrev: string; score: number; }; gameState?: string; gameScheduleState?: string; } // Register NHL API tools server.tool( "get-current-standings", "Get current NHL standings", {}, async () => { const standings = await makeNHLRequest<StandingsResponse>(`${NHL_WEB_API_BASE}/standings/now`); if (!standings) { return { content: [ { type: "text", text: "Failed to retrieve NHL standings. The NHL API might be unavailable.", }, ], }; } // Format standings for display const formattedStandings = []; if (standings.standings && standings.standings.length > 0) { // Group by division const divisionMap = new Map(); for (const team of standings.standings) { const divisionName = team.divisionName || "Unknown Division"; if (!divisionMap.has(divisionName)) { divisionMap.set(divisionName, []); } divisionMap.get(divisionName).push(team); } // Format each division for (const [division, teams] of divisionMap.entries()) { formattedStandings.push(`\n## ${division} Division`); formattedStandings.push("Team | GP | W | L | OTL | PTS | GF | GA"); formattedStandings.push("-----|----|----|----|----|-----|----|----|"); // Sort teams by points teams.sort((a: any, b: any) => (b.points || 0) - (a.points || 0)); for (const team of teams) { const teamName = team.teamName?.default || "Unknown"; const gp = (team.wins || 0) + (team.losses || 0) + (team.otLosses || 0); formattedStandings.push( `${teamName} | ${gp} | ${team.wins || 0} | ${team.losses || 0} | ${team.otLosses || 0} | ${team.points || 0} | ${team.goalsFor || 0} | ${team.goalAgainst || 0}` ); } } } else { formattedStandings.push("No standings data available."); } return { content: [ { type: "text", text: formattedStandings.join("\n"), }, ], }; } ); server.tool( "get-team", "Get information about an NHL team", { teamAbbrev: z.string().length(3).describe("Three-letter team abbreviation (e.g. TOR, NYR, BOS)"), }, async ({ teamAbbrev }: { teamAbbrev: string }) => { const teamCode = teamAbbrev.toUpperCase().trim(); console.error(`Getting information for team: ${teamCode}`); // List of endpoints to try in order const endpoints = [ { name: "Team Info API", url: `${NHL_STATS_API_BASE}/team`, parseTeam: (data: any): TeamResponse => { console.error("Team Info API data keys:", Object.keys(data)); const teams = data.data || []; const matchingTeam = teams.find((t: any) => t.triCode === teamCode || t.rawTricode === teamCode || t.abbrev === teamCode || t.teamAbbrev?.default === teamCode ); if (!matchingTeam) { console.error("No matching team found in team info data"); return {}; } return { teamId: matchingTeam.id || matchingTeam.teamId, name: matchingTeam.fullName || matchingTeam.name, abbrev: teamCode, locationName: matchingTeam.locationName, teamName: matchingTeam.teamName, division: { name: matchingTeam.divisionName || (matchingTeam.division && matchingTeam.division.name) || "Unknown", shortName: matchingTeam.divisionAbbrev || (matchingTeam.division && matchingTeam.division.nameShort) || "" }, conference: { name: matchingTeam.conferenceName || (matchingTeam.conference && matchingTeam.conference.name) || "Unknown", shortName: matchingTeam.conferenceAbbrev || (matchingTeam.conference && matchingTeam.conference.nameShort) || "" } }; } }, { name: "Standings API", url: `${NHL_WEB_API_BASE}/standings/now`, parseTeam: (data: any): TeamResponse => { console.error("Standings API data keys:", Object.keys(data)); let foundTeam; // Navigate through possible data structures const standings = data.standings || []; for (const team of standings) { if (team.teamAbbrev?.default === teamCode || team.abbrev === teamCode || team.triCode === teamCode) { foundTeam = team; break; } } if (!foundTeam) { console.error("No matching team found in standings data"); return {}; } return { teamId: foundTeam.teamId || foundTeam.id, name: (foundTeam.teamName && foundTeam.teamName.default) || foundTeam.name, abbrev: teamCode, locationName: foundTeam.placeName?.default, teamName: foundTeam.teamCommonName?.default, division: { name: foundTeam.divisionName || "Unknown Division", shortName: foundTeam.divisionAbbrev || "" }, conference: { name: foundTeam.conferenceName || (foundTeam.conferenceAbbrev === "E" ? "Eastern" : foundTeam.conferenceAbbrev === "W" ? "Western" : "Unknown"), shortName: foundTeam.conferenceAbbrev || "" }, record: { wins: foundTeam.wins || 0, losses: foundTeam.losses || 0, otLosses: foundTeam.otLosses || 0, points: foundTeam.points || 0, } }; } } ]; // Try each endpoint until we get valid team data let teamData: TeamResponse = {}; let errorMessages = []; for (const endpoint of endpoints) { try { console.error(`Trying ${endpoint.name} endpoint: ${endpoint.url}`); const data = await makeNHLRequest<any>(endpoint.url); if (data) { console.error(`Got response from ${endpoint.name}`); const parsedTeam = endpoint.parseTeam(data); // Merge with existing data, keeping non-empty values teamData = { ...teamData, ...Object.fromEntries( Object.entries(parsedTeam).filter(([_, v]) => v !== undefined && v !== null) ) }; // If we have essential data, we can stop trying more endpoints if (teamData.name && teamData.division && teamData.conference) { console.error(`Got complete team data from ${endpoint.name}`); break; } } } catch (error) { const message = `Error with ${endpoint.name}: ${error}`; console.error(message); errorMessages.push(message); } } // Verify we have the minimum required data if (!teamData.name) { console.error(`Failed to retrieve data for team: ${teamCode}. Errors: ${errorMessages.join("; ")}`); return { content: [ { type: "text", text: `Failed to retrieve data for team: ${teamCode}. The team code may be invalid or NHL API might be unavailable.`, }, ], }; } // Get team stats const statsUrl = `${NHL_WEB_API_BASE}/club-stats/${teamCode}/now`; const statsData = await makeNHLRequest<any>(statsUrl); // Construct the team name properly const teamFullName = teamData.name || (teamData.locationName && teamData.teamName) ? `${teamData.locationName} ${teamData.teamName}` : `${teamCode} (name unavailable)`; let teamInfo = [ `Team: ${teamFullName}`, `Abbreviation: ${teamCode}`, `Division: ${teamData.division?.name || "Not available"}`, `Conference: ${teamData.conference?.name || "Not available"}`, ]; if (statsData && statsData.teamStats && statsData.teamStats.length > 0) { const stats = statsData.teamStats[0]; teamInfo = [ ...teamInfo, "Stats:", ` Record: ${stats.wins}-${stats.losses}-${stats.otLosses}`, ` Points: ${stats.points}`, ` Goals For: ${stats.goalsFor}`, ` Goals Against: ${stats.goalsAgainst}`, ` Home Record: ${stats.homeRecord || stats.homeRecordL10 || "N/A"}`, ` Away Record: ${stats.awayRecord || stats.awayRecordL10 || "N/A"}`, ]; } else if (teamData.record) { // Fallback to basic record data from standings teamInfo = [ ...teamInfo, "Stats:", ` Record: ${teamData.record.wins}-${teamData.record.losses}-${teamData.record.otLosses}`, ` Points: ${teamData.record.points}`, ]; } return { content: [ { type: "text", text: teamInfo.join("\n"), }, ], }; }, ); server.tool( "get-team-roster", "Get information about an NHL team's current roster", { teamAbbrev: z.string().length(3).describe("Three-letter team abbreviation (e.g. TOR, NYR, BOS)"), seasonAbbrev: z.string().describe("Season code (e.g. 20242025 for the 2024-2025 season)"), }, async ({ teamAbbrev, seasonAbbrev }: { teamAbbrev: string; seasonAbbrev: string }) => { const teamCode = teamAbbrev.toUpperCase(); const seasonCode = seasonAbbrev; console.error(`Getting roster for team: ${teamCode} in season: ${seasonCode}`); // Fetch roster data from NHL API const rosterUrl = `${NHL_WEB_API_BASE}/roster/${teamCode}/${seasonCode}`; let rosterData = null; try { console.error(`Fetching roster from: ${rosterUrl}`); rosterData = await makeNHLRequest<any>(rosterUrl); if (!rosterData) { return { content: [ { type: "text", text: `Failed to retrieve roster for ${teamCode} in season ${seasonCode}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching roster: ${error}`); return { content: [ { type: "text", text: `Error retrieving roster for ${teamCode} in season ${seasonCode}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Format player information const formatPlayer = (player: any): string => { const name = `${safeString(player.firstName)} ${safeString(player.lastName)}`; const number = player.sweaterNumber ? `#${player.sweaterNumber}` : ""; const position = player.positionCode || ""; const height = player.heightInInches ? `${Math.floor(player.heightInInches / 12)}'${player.heightInInches % 12}"` : "N/A"; const weight = player.weightInPounds ? `${player.weightInPounds} lbs` : "N/A"; return `${name} ${number} (${position}) - ${height}, ${weight}`; }; // Build the roster display const rosterDisplay = []; // Add team header rosterDisplay.push(`# ${teamCode} Roster - ${seasonCode} Season\n`); // Add forwards if (rosterData.forwards && rosterData.forwards.length > 0) { rosterDisplay.push("## Forwards"); rosterData.forwards.forEach((player: any) => { rosterDisplay.push(formatPlayer(player)); }); rosterDisplay.push(""); } // Add defensemen if (rosterData.defensemen && rosterData.defensemen.length > 0) { rosterDisplay.push("## Defensemen"); rosterData.defensemen.forEach((player: any) => { rosterDisplay.push(formatPlayer(player)); }); rosterDisplay.push(""); } // Add goalies if (rosterData.goalies && rosterData.goalies.length > 0) { rosterDisplay.push("## Goalies"); rosterData.goalies.forEach((player: any) => { rosterDisplay.push(formatPlayer(player)); }); rosterDisplay.push(""); } // Add roster summary const totalPlayers = (rosterData.forwards?.length || 0) + (rosterData.defensemen?.length || 0) + (rosterData.goalies?.length || 0); rosterDisplay.push(`Total players: ${totalPlayers}`); return { content: [ { type: "text", text: rosterDisplay.join("\n"), }, ], }; } ); server.tool( "get-team-stats", "Get statistics for an NHL team", { teamAbbrev: z.string().length(3).describe("Three-letter team abbreviation (e.g. TOR, NYR, BOS)"), }, async ({ teamAbbrev }: { teamAbbrev: string }) => { return { content: [ { type: "text", text: `Team stats for ${teamAbbrev} will be implemented soon.`, }, ], }; } ); server.tool( "get-player-landing", "Get information about an NHL player", { playerId: z.number().describe("NHL player ID (e.g. 8478402 for Connor McDavid)"), }, async ({ playerId }: { playerId: number }) => { console.error(`Getting information for player ID: ${playerId}`); // Use only the Player Landing API since it's the one that works const playerLandingUrl = `${NHL_WEB_API_BASE}/player/${playerId}/landing`; let playerData: PlayerResponse | null = null; let errorMessage = ""; try { console.error(`Trying Player Landing API: ${playerLandingUrl}`); const data = await makeNHLRequest<any>(playerLandingUrl); if (data) { console.error("Got response from Player Landing API"); playerData = data; console.error("Got player data from Player Landing API"); } } catch (error) { errorMessage = `Error with Player Landing API: ${error}`; console.error(errorMessage); } if (!playerData) { console.error(`Failed to retrieve data for player ID: ${playerId}. Error: ${errorMessage}`); return { content: [ { type: "text", text: `Failed to retrieve data for player ID: ${playerId}. The player ID may be invalid or NHL API might be unavailable.`, }, ], }; } // Get player stats if not already included if (!playerData.featuredStats) { const statsUrl = `${NHL_WEB_API_BASE}/player/${playerId}/game-log/now`; try { const statsData = await makeNHLRequest<any>(statsUrl); console.error("Retrieved player stats data"); if (statsData && statsData.stats) { playerData.featuredStats = { regularSeason: { subSeason: statsData.stats } }; } } catch (error) { console.error("Error retrieving player stats:", error); } } // Get their team information if not already included let teamName = playerData.fullTeamName?.default || "Unknown Team"; let teamAbbrev = playerData.currentTeamAbbrev || ""; // Safe string extraction functions to prevent [object Object] issues const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; // Return default for objects/arrays }; // Format the player's name properly let playerName = `${safeString(playerData.firstName)} ${safeString(playerData.lastName)}`; if (playerName.trim() === '') { playerName = playerData.playerSlug || `Player #${playerId}`; } // Format the birth place properly const birthCity = safeString(playerData.birthCity, "Unknown City"); const birthStateProvince = safeString(playerData.birthStateProvince, ""); const birthCountry = safeString(playerData.birthCountry, "Unknown Country"); const birthPlace = `${birthCity}${birthStateProvince ? ', ' + birthStateProvince : ''}${birthCountry !== "Unknown Country" ? ', ' + birthCountry : ''}`; // Format height const height = playerData.heightInInches ? `${Math.floor(playerData.heightInInches / 12)}'${playerData.heightInInches % 12}" (${playerData.heightInCentimeters || Math.round(playerData.heightInInches * 2.54)} cm)` : "N/A"; // Format weight const weight = playerData.weightInPounds ? `${playerData.weightInPounds} lbs (${playerData.weightInKilograms || Math.round(playerData.weightInPounds * 0.453592)} kg)` : "N/A"; const playerInfo = [ `Name: ${playerName}`, `Position: ${safeString(playerData.position, "Unknown")}`, `Jersey Number: ${safeString(playerData.sweaterNumber, "N/A")}`, `Team: ${teamName}`, `Birth Date: ${safeString(playerData.birthDate, "Unknown")}`, `Birth Place: ${birthPlace}`, `Height: ${height}`, `Weight: ${weight}`, `Shoots/Catches: ${safeString(playerData.shootsCatches, "N/A")}`, ]; // Add draft information if available if (playerData.draftDetails) { const draft = playerData.draftDetails; if (draft.year) { playerInfo.push( `Draft: ${draft.year} Round ${draft.round || 'N/A'}, Pick ${draft.overallPick || 'N/A'} (${draft.teamAbbrev || 'N/A'})` ); } } // Add current season stats if available if (playerData.featuredStats?.regularSeason?.subSeason) { const stats = playerData.featuredStats.regularSeason.subSeason; const isGoalie = playerData.position === "G"; if (isGoalie) { playerInfo.push( "Season Stats (Goalie):", ` Games Played: ${stats.gamesPlayed || 0}`, ` Wins: ${stats.wins || 0}`, ` Losses: ${stats.losses || 0}`, ` OT Losses: ${stats.otLosses || 0}`, ` Save Percentage: ${stats.savePctg || 0}`, ` Goals Against Average: ${stats.goalsAgainstAverage || 0}`, ` Shutouts: ${stats.shutouts || 0}` ); } else { playerInfo.push( "Season Stats (Skater):", ` Games Played: ${stats.gamesPlayed || 0}`, ` Goals: ${stats.goals || 0}`, ` Assists: ${stats.assists || 0}`, ` Points: ${stats.points || 0}`, ` Plus/Minus: ${stats.plusMinus || 0}`, ` Penalty Minutes: ${stats.pim || 0}`, ` Power Play Goals: ${stats.powerPlayGoals || 0}`, ` Game Winning Goals: ${stats.gameWinningGoals || 0}`, ` Shots: ${stats.shots || 0}`, ` Shooting Percentage: ${stats.shootingPctg ? `${stats.shootingPctg * 100}%` : "0%"}` ); } } // Add career stats if available if (playerData.featuredStats?.regularSeason?.career) { const career = playerData.featuredStats.regularSeason.career; playerInfo.push( "Career Stats:", ` Games Played: ${career.gamesPlayed || 0}`, ` Goals: ${career.goals || 0}`, ` Assists: ${career.assists || 0}`, ` Points: ${career.points || 0}` ); } // Add awards if available if (playerData.awards && playerData.awards.length > 0) { playerInfo.push("Awards:"); for (const award of playerData.awards) { playerInfo.push(` ${safeString(award.trophy)}`); } } return { content: [ { type: "text", text: playerInfo.join("\n"), }, ], }; }, ); server.tool( "get-schedule", "Get NHL game schedule", { teamAbbrev: z.string().length(3).optional().describe("Three-letter team abbreviation to filter by (e.g. TOR, NYR)"), date: z.string().optional().describe("Date in YYYY-MM-DD format (defaults to today)"), }, async ({ teamAbbrev, date }: { teamAbbrev?: string; date?: string }) => { let scheduleUrl; if (teamAbbrev) { const teamCode = teamAbbrev.toUpperCase(); scheduleUrl = date ? `${NHL_WEB_API_BASE}/club-schedule/${teamCode}/week/${date}` : `${NHL_WEB_API_BASE}/club-schedule/${teamCode}/week/now`; } else { scheduleUrl = date ? `${NHL_WEB_API_BASE}/schedule/${date}` : `${NHL_WEB_API_BASE}/schedule/now`; } const scheduleData = await makeNHLRequest<ScheduleResponse>(scheduleUrl); if (!scheduleData || !scheduleData.gameWeek) { return { content: [ { type: "text", text: "Failed to retrieve NHL schedule", }, ], }; } const gameWeeks = scheduleData.gameWeek || []; if (gameWeeks.length === 0) { return { content: [ { type: "text", text: "No games scheduled for the requested period", }, ], }; } // Format schedule const formattedSchedule = gameWeeks.map(day => { const games = day.games || []; const gameList = games.map(game => { const homeTeam = game.homeTeam; const awayTeam = game.awayTeam; const startTime = game.startTimeUTC ? new Date(game.startTimeUTC).toLocaleTimeString() : "TBD"; let gameInfo = `${awayTeam?.abbrev} @ ${homeTeam?.abbrev} - ${startTime}`; // Add score if game has started/completed if (game.gameState === "LIVE" || game.gameState === "FINAL") { gameInfo += ` (${awayTeam?.score} - ${homeTeam?.score})`; gameInfo += game.gameState === "FINAL" ? " FINAL" : " LIVE"; } return gameInfo; }); return [ `${day.date} (${day.dayAbbrev}) - ${day.numberOfGames} games:`, ...gameList, "", ].join("\n"); }); return { content: [ { type: "text", text: formattedSchedule.join("\n"), }, ], }; }, ); server.tool( "get-skater-leaders", "Get NHL statistical leaders for skaters", { category: z.enum(["points", "goals", "assists", "plusMinus", "powerPlayGoals", "gameWinningGoals", "shots"]) .describe("Statistical category to get leaders for"), limit: z.number().min(1).max(50).default(10).describe("Number of players to return (max 50)"), }, async ({ category, limit }: { category: string; limit: number }) => { console.error(`Getting statistical leaders for category: ${category}, limit: ${limit}`); // Define the set of URLs to try in order of preference const urls = [ // Primary NHL web API { url: `${NHL_WEB_API_BASE}/skater-stats-leaders/current?categories=${category}&limit=${limit}`, format: "web", extract: (data: any) => { if (!data || !data.categories || data.categories.length === 0) { console.error("No categories in web API response"); return null; } const categoryData = data.categories.find((c: any) => c.categoryName?.toLowerCase() === category.toLowerCase() || c.categoryLabel?.toLowerCase() === category.toLowerCase() ) || data.categories[0]; return { leaders: categoryData.leaders || [], categoryLabel: categoryData.categoryLabel || category, season: data.season || 'current' }; } }, // Secondary stats API direct endpoint { url: `${NHL_STATS_API_BASE}/leaders/skaters/${category}?limit=${limit}`, format: "stats-direct", extract: (data: any) => { if (!data || !data.data) { console.error("No data in stats API response"); return null; } return { leaders: data.data, categoryLabel: data.description || category, season: data.season || 'current' }; } }, // Skater stats with filter as last resort { url: `${NHL_STATS_API_BASE}/skater/summary?limit=${limit}&sort=${category}&cayenneExp=seasonId=20242025`, format: "stats-filtered", extract: (data: any) => { if (!data || !data.data) { console.error("No data in filtered stats API response"); return null; } return { leaders: data.data, categoryLabel: category, season: '2024-2025' }; } } ]; // Try all URLs in sequence let result = null; let errors = []; for (const endpoint of urls) { try { console.error(`Trying to fetch leader data from: ${endpoint.url}`); const data = await makeNHLRequest<any>(endpoint.url); if (!data) { console.error(`No data returned from ${endpoint.url}`); continue; } console.error(`Got response from ${endpoint.format} format endpoint`); const extracted = endpoint.extract(data); if (extracted && extracted.leaders && extracted.leaders.length > 0) { result = extracted; console.error(`Successfully extracted ${extracted.leaders.length} leaders using ${endpoint.format} format`); break; } else { console.error(`Failed to extract leaders from ${endpoint.format} format`); } } catch (error) { const message = `Error with ${endpoint.format} endpoint: ${error}`; console.error(message); errors.push(message); } } // If we couldn't get any data if (!result) { return { content: [ { type: "text", text: `Failed to retrieve ${category} leaders. The NHL API endpoints may be unavailable or the category name may be invalid. Valid categories are: points, goals, assists, plusMinus, powerPlayGoals, gameWinningGoals, shots.`, }, ], }; } const { leaders, categoryLabel, season } = result; // Extra safety check if (leaders.length === 0) { return { content: [ { type: "text", text: `No statistical leaders found for category: ${category}`, }, ], }; } // Safe string extraction function to prevent object serialization issues const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); return defaultValue; }; // Format leaders const headerText = `NHL Leaders - ${categoryLabel} (${season})`; const leaderRows = leaders.map((leader: any, index: number) => { // Handle different API formats with safe string extraction let name = 'Unknown Player'; if (typeof leader.fullName === 'string') { name = leader.fullName; } else if (typeof leader.skaterFullName === 'string') { name = leader.skaterFullName; } else if (leader.firstName && leader.lastName) { name = `${safeString(leader.firstName)} ${safeString(leader.lastName)}`; } else { name = `Player #${leader.playerId || leader.id || index+1}`; } // Extract team abbreviation safely let team = 'N/A'; if (typeof leader.teamAbbrev === 'string') { team = leader.teamAbbrev; } else if (typeof leader.teamAbbrevs === 'string') { team = leader.teamAbbrevs; } else if (leader.team && typeof leader.team.triCode === 'string') { team = leader.team.triCode; } else if (leader.team && typeof leader.team.abbrev === 'string') { team = leader.team.abbrev; } // Extract value based on category let value = '0'; if (leader.value !== undefined) { value = safeString(leader.value); } else if (category === 'points' && leader.points !== undefined) { value = safeString(leader.points); } else if (category === 'goals' && leader.goals !== undefined) { value = safeString(leader.goals); } else if (category === 'assists' && leader.assists !== undefined) { value = safeString(leader.assists); } else if (category === 'plusMinus' && leader.plusMinus !== undefined) { value = safeString(leader.plusMinus); } else if (leader[category] !== undefined) { value = safeString(leader[category]); } return [ `${(index + 1).toString().padStart(2)}. `, `${name.padEnd(25)} | `, `${team.padEnd(4)} | `, `${value}`, ].join(""); }); return { content: [ { type: "text", text: [ headerText, "".padEnd(headerText.length, "="), ...leaderRows, ].join("\n"), }, ], }; }, ); server.tool( "get-current-stat-leaders", "Get current NHL skater stats leaders", { category: z.string().describe("Stat category (e.g. goals, assists, points)"), limit: z.number().optional().describe("Number of players to return (default: 5)"), }, async ({ category, limit = 5 }: { category: string; limit?: number }) => { console.error(`Getting stats leaders for category: ${category}, limit: ${limit}`); // Fetch stats leaders data from NHL API const leadersUrl = `${NHL_WEB_API_BASE}/skater-stats-leaders/current?categories=${category}&limit=${limit}`; let leadersData = null; try { console.error(`Fetching stats leaders from: ${leadersUrl}`); leadersData = await makeNHLRequest<any>(leadersUrl); if (!leadersData) { return { content: [ { type: "text", text: `Failed to retrieve stats leaders for category: ${category}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching stats leaders: ${error}`); return { content: [ { type: "text", text: `Error retrieving stats leaders for category: ${category}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Check if we have data for the requested category if (!leadersData[category] || !Array.isArray(leadersData[category]) || leadersData[category].length === 0) { return { content: [ { type: "text", text: `No data found for category: ${category}. Available categories may include: goals, assists, points, etc.`, }, ], }; } // Format the leaders data const leaders = leadersData[category]; const formattedLeaders = []; // Add header formattedLeaders.push(`# NHL ${category.charAt(0).toUpperCase() + category.slice(1)} Leaders\n`); // Add table header formattedLeaders.push("| Rank | Player | Team | Pos | Value |"); formattedLeaders.push("|------|--------|------|-----|-------|"); // Add each player leaders.forEach((player: any, index: number) => { const rank = index + 1; const name = `${safeString(player.firstName)} ${safeString(player.lastName)}`; const team = safeString(player.teamAbbrev); const position = safeString(player.position); const value = player.value !== undefined ? player.value : "N/A"; formattedLeaders.push(`| ${rank} | ${name} | ${team} | ${position} | ${value} |`); }); return { content: [ { type: "text", text: formattedLeaders.join("\n"), }, ], }; } ); server.tool( "get-season-stat-leaders", "Get NHL skater stats leaders for a specific season and game type", { category: z.string().describe("Stat category (e.g. goals, assists, points)"), season: z.string().describe("Season in YYYYYYYY format (e.g. 20232024)"), gameType: z.number().default(2).describe("Game type (2 for regular season, 3 for playoffs)"), limit: z.number().optional().describe("Number of players to return (default: 5)"), }, async ({ category, season, gameType = 2, limit = 5 }: { category: string; season: string; gameType?: number; limit?: number }) => { console.error(`Getting stats leaders for category: ${category}, season: ${season}, gameType: ${gameType}, limit: ${limit}`); // Fetch stats leaders data from NHL API const leadersUrl = `${NHL_WEB_API_BASE}/skater-stats-leaders/${season}/${gameType}?categories=${category}&limit=${limit}`; let leadersData = null; try { console.error(`Fetching stats leaders from: ${leadersUrl}`); leadersData = await makeNHLRequest<any>(leadersUrl); if (!leadersData) { return { content: [ { type: "text", text: `Failed to retrieve stats leaders for category: ${category}, season: ${season}, gameType: ${gameType}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching stats leaders: ${error}`); return { content: [ { type: "text", text: `Error retrieving stats leaders for category: ${category}, season: ${season}, gameType: ${gameType}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Check if we have data for the requested category if (!leadersData[category] || !Array.isArray(leadersData[category]) || leadersData[category].length === 0) { return { content: [ { type: "text", text: `No data found for category: ${category}, season: ${season}, gameType: ${gameType}. Available categories may include: goals, assists, points, etc.`, }, ], }; } // Format the leaders data const leaders = leadersData[category]; const formattedLeaders = []; // Add header const seasonFormatted = `${season.substring(0, 4)}-${season.substring(4)}`; const gameTypeText = gameType === 2 ? "Regular Season" : gameType === 3 ? "Playoffs" : `Game Type ${gameType}`; formattedLeaders.push(`# NHL ${category.charAt(0).toUpperCase() + category.slice(1)} Leaders - ${seasonFormatted} ${gameTypeText}\n`); // Add table header formattedLeaders.push("| Rank | Player | Team | Pos | Value |"); formattedLeaders.push("|------|--------|------|-----|-------|"); // Add each player leaders.forEach((player: any, index: number) => { const rank = index + 1; const name = `${safeString(player.firstName)} ${safeString(player.lastName)}`; const team = safeString(player.teamAbbrev); const position = safeString(player.position); const value = player.value !== undefined ? player.value : "N/A"; formattedLeaders.push(`| ${rank} | ${name} | ${team} | ${position} | ${value} |`); }); return { content: [ { type: "text", text: formattedLeaders.join("\n"), }, ], }; } ); server.tool( "get-current-goalie-leaders", "Get current NHL goalie stats leaders", { category: z.string().describe("Stat category (e.g. wins, savePctg, gaa, shutouts)"), limit: z.number().optional().describe("Number of goalies to return (default: 5)"), }, async ({ category, limit = 5 }: { category: string; limit?: number }) => { console.error(`Getting goalie stats leaders for category: ${category}, limit: ${limit}`); // Fetch goalie stats leaders data from NHL API const leadersUrl = `${NHL_WEB_API_BASE}/goalie-stats-leaders/current?categories=${category}&limit=${limit}`; let leadersData = null; try { console.error(`Fetching goalie stats leaders from: ${leadersUrl}`); leadersData = await makeNHLRequest<any>(leadersUrl); if (!leadersData) { return { content: [ { type: "text", text: `Failed to retrieve goalie stats leaders for category: ${category}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching goalie stats leaders: ${error}`); return { content: [ { type: "text", text: `Error retrieving goalie stats leaders for category: ${category}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Check if we have data for the requested category if (!leadersData[category] || !Array.isArray(leadersData[category]) || leadersData[category].length === 0) { return { content: [ { type: "text", text: `No data found for category: ${category}. Available categories may include: wins, savePctg, gaa, shutouts, etc.`, }, ], }; } // Format the leaders data const leaders = leadersData[category]; const formattedLeaders = []; // Add header const categoryDisplay = category === "savePctg" ? "Save Percentage" : category === "gaa" ? "Goals Against Average" : category.charAt(0).toUpperCase() + category.slice(1); formattedLeaders.push(`# NHL Goalie ${categoryDisplay} Leaders\n`); // Add table header formattedLeaders.push("| Rank | Goalie | Team | Value | GP |"); formattedLeaders.push("|------|--------|------|-------|-----|"); // Add each goalie leaders.forEach((goalie: any, index: number) => { const rank = index + 1; const name = `${safeString(goalie.firstName)} ${safeString(goalie.lastName)}`; const team = safeString(goalie.teamAbbrev); const value = goalie.value !== undefined ? (category === "savePctg" ? goalie.value.toFixed(3) : goalie.value) : "N/A"; const gamesPlayed = goalie.gamesPlayed !== undefined ? goalie.gamesPlayed : "N/A"; formattedLeaders.push(`| ${rank} | ${name} | ${team} | ${value} | ${gamesPlayed} |`); }); return { content: [ { type: "text", text: formattedLeaders.join("\n"), }, ], }; } ); server.tool( "get-season-goalie-leaders", "Get NHL goalie stats leaders for a specific season and game type", { category: z.string().describe("Stat category (e.g. wins, savePctg, gaa, shutouts)"), season: z.string().describe("Season in YYYYYYYY format (e.g. 20232024)"), gameType: z.number().default(2).describe("Game type (2 for regular season, 3 for playoffs)"), limit: z.number().optional().describe("Number of goalies to return (default: 5)"), }, async ({ category, season, gameType = 2, limit = 5 }: { category: string; season: string; gameType?: number; limit?: number }) => { console.error(`Getting goalie stats leaders for category: ${category}, season: ${season}, gameType: ${gameType}, limit: ${limit}`); // Fetch goalie stats leaders data from NHL API const leadersUrl = `${NHL_WEB_API_BASE}/goalie-stats-leaders/${season}/${gameType}?categories=${category}&limit=${limit}`; let leadersData = null; try { console.error(`Fetching goalie stats leaders from: ${leadersUrl}`); leadersData = await makeNHLRequest<any>(leadersUrl); if (!leadersData) { return { content: [ { type: "text", text: `Failed to retrieve goalie stats leaders for category: ${category}, season: ${season}, gameType: ${gameType}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching goalie stats leaders: ${error}`); return { content: [ { type: "text", text: `Error retrieving goalie stats leaders for category: ${category}, season: ${season}, gameType: ${gameType}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Check if we have data for the requested category if (!leadersData[category] || !Array.isArray(leadersData[category]) || leadersData[category].length === 0) { return { content: [ { type: "text", text: `No data found for category: ${category}, season: ${season}, gameType: ${gameType}. Available categories may include: wins, savePctg, gaa, shutouts, etc.`, }, ], }; } // Format the leaders data const leaders = leadersData[category]; const formattedLeaders = []; // Add header const seasonFormatted = `${season.substring(0, 4)}-${season.substring(4)}`; const gameTypeText = gameType === 2 ? "Regular Season" : gameType === 3 ? "Playoffs" : `Game Type ${gameType}`; const categoryDisplay = category === "savePctg" ? "Save Percentage" : category === "gaa" ? "Goals Against Average" : category.charAt(0).toUpperCase() + category.slice(1); formattedLeaders.push(`# NHL Goalie ${categoryDisplay} Leaders - ${seasonFormatted} ${gameTypeText}\n`); // Add table header formattedLeaders.push("| Rank | Goalie | Team | Value | GP |"); formattedLeaders.push("|------|--------|------|-------|-----|"); // Add each goalie leaders.forEach((goalie: any, index: number) => { const rank = index + 1; const name = `${safeString(goalie.firstName)} ${safeString(goalie.lastName)}`; const team = safeString(goalie.teamAbbrev); const value = goalie.value !== undefined ? (category === "savePctg" ? goalie.value.toFixed(3) : goalie.value) : "N/A"; const gamesPlayed = goalie.gamesPlayed !== undefined ? goalie.gamesPlayed : "N/A"; formattedLeaders.push(`| ${rank} | ${name} | ${team} | ${value} | ${gamesPlayed} |`); }); return { content: [ { type: "text", text: formattedLeaders.join("\n"), }, ], }; } ); server.tool( "get-current-team-stats", "Get current statistics for an NHL team", { teamAbbrev: z.string().length(3).describe("Three-letter team abbreviation (e.g. TOR, NYR, BOS)"), }, async ({ teamAbbrev }: { teamAbbrev: string }) => { console.error(`Getting current stats for team: ${teamAbbrev}`); // Fetch team stats data from NHL API const statsUrl = `${NHL_WEB_API_BASE}/club-stats/${teamAbbrev}/now`; let statsData = null; try { console.error(`Fetching team stats from: ${statsUrl}`); statsData = await makeNHLRequest<any>(statsUrl); if (!statsData) { return { content: [ { type: "text", text: `Failed to retrieve current stats for team: ${teamAbbrev}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching team stats: ${error}`); return { content: [ { type: "text", text: `Error retrieving current stats for team: ${teamAbbrev}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Format the team stats data const formattedStats = []; // Add team header formattedStats.push(`# ${teamAbbrev} Current Team Statistics\n`); // Add season info if available if (statsData.season) { const seasonFormatted = `${statsData.season.substring(0, 4)}-${statsData.season.substring(4)}`; formattedStats.push(`Season: ${seasonFormatted}`); } // Add team record if available if (statsData.wins !== undefined && statsData.losses !== undefined && statsData.otLosses !== undefined) { const points = statsData.points !== undefined ? statsData.points : (statsData.wins * 2 + statsData.otLosses); formattedStats.push(`Record: ${statsData.wins}-${statsData.losses}-${statsData.otLosses} (${points} points)\n`); } // Add skater stats section if (statsData.skaters && statsData.skaters.length > 0) { formattedStats.push(`## Skaters (${statsData.skaters.length})\n`); // Add table header for skaters formattedStats.push("| Player | Pos | GP | G | A | P | +/- | PIM | PPG | SHG | GWG | S | S% |"); formattedStats.push("|--------|-----|----|----|----|----|-----|-----|-----|-----|-----|----|----|"); // Sort skaters by points (descending) const sortedSkaters = [...statsData.skaters].sort((a, b) => (b.points || 0) - (a.points || 0)); // Add each skater sortedSkaters.forEach((skater) => { const firstName = safeString(skater.firstName?.default, ""); const lastName = safeString(skater.lastName?.default, ""); const name = `${firstName} ${lastName}`.trim(); const position = safeString(skater.positionCode, ""); const gamesPlayed = skater.gamesPlayed !== undefined ? skater.gamesPlayed : "0"; const goals = skater.goals !== undefined ? skater.goals : "0"; const assists = skater.assists !== undefined ? skater.assists : "0"; const points = skater.points !== undefined ? skater.points : "0"; const plusMinus = skater.plusMinus !== undefined ? (skater.plusMinus > 0 ? `+${skater.plusMinus}` : skater.plusMinus) : "0"; const pim = skater.penaltyMinutes !== undefined ? skater.penaltyMinutes : "0"; const ppg = skater.powerPlayGoals !== undefined ? skater.powerPlayGoals : "0"; const shg = skater.shorthandedGoals !== undefined ? skater.shorthandedGoals : "0"; const gwg = skater.gameWinningGoals !== undefined ? skater.gameWinningGoals : "0"; const shots = skater.shots !== undefined ? skater.shots : "0"; const shootingPct = skater.shootingPctg !== undefined ? skater.shootingPctg.toFixed(1) : "0.0"; formattedStats.push(`| ${name} | ${position} | ${gamesPlayed} | ${goals} | ${assists} | ${points} | ${plusMinus} | ${pim} | ${ppg} | ${shg} | ${gwg} | ${shots} | ${shootingPct} |`); }); formattedStats.push(""); } // Add goalie stats section if (statsData.goalies && statsData.goalies.length > 0) { formattedStats.push(`## Goalies (${statsData.goalies.length})\n`); // Add table header for goalies formattedStats.push("| Goalie | GP | GS | W | L | OTL | GAA | SV% | SO |"); formattedStats.push("|--------|----|----|---|---|-----|-----|-----|---|"); // Sort goalies by games played (descending) const sortedGoalies = [...statsData.goalies].sort((a, b) => (b.gamesPlayed || 0) - (a.gamesPlayed || 0)); // Add each goalie sortedGoalies.forEach((goalie) => { const firstName = safeString(goalie.firstName?.default, ""); const lastName = safeString(goalie.lastName?.default, ""); const name = `${firstName} ${lastName}`.trim(); const gamesPlayed = goalie.gamesPlayed !== undefined ? goalie.gamesPlayed : "0"; const gamesStarted = goalie.gamesStarted !== undefined ? goalie.gamesStarted : "0"; const wins = goalie.wins !== undefined ? goalie.wins : "0"; const losses = goalie.losses !== undefined ? goalie.losses : "0"; const otLosses = goalie.overtimeLosses !== undefined ? goalie.overtimeLosses : "0"; const gaa = goalie.goalsAgainstAverage !== undefined ? goalie.goalsAgainstAverage.toFixed(2) : "0.00"; const svPct = goalie.savePercentage !== undefined ? goalie.savePercentage.toFixed(3) : "0.000"; const shutouts = goalie.shutouts !== undefined ? goalie.shutouts : "0"; formattedStats.push(`| ${name} | ${gamesPlayed} | ${gamesStarted} | ${wins} | ${losses} | ${otLosses} | ${gaa} | ${svPct} | ${shutouts} |`); }); } return { content: [ { type: "text", text: formattedStats.join("\n"), }, ], }; } ); server.tool( "get-team-stats-seasons", "Get available seasons with statistics for an NHL team", { teamAbbrev: z.string().length(3).describe("Three-letter team abbreviation (e.g. TOR, NYR, BOS)"), }, async ({ teamAbbrev }: { teamAbbrev: string }) => { console.error(`Getting available stats seasons for team: ${teamAbbrev}`); // Fetch team stats seasons data from NHL API const seasonsUrl = `${NHL_WEB_API_BASE}/club-stats-season/${teamAbbrev}`; let seasonsData = null; try { console.error(`Fetching team stats seasons from: ${seasonsUrl}`); seasonsData = await makeNHLRequest<any>(seasonsUrl); if (!seasonsData) { return { content: [ { type: "text", text: `Failed to retrieve available stats seasons for team: ${teamAbbrev}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching team stats seasons: ${error}`); return { content: [ { type: "text", text: `Error retrieving available stats seasons for team: ${teamAbbrev}: ${error}`, }, ], }; } // Format the seasons data const formattedSeasons = []; // Add header formattedSeasons.push(`# ${teamAbbrev} Available Statistics Seasons\n`); // Check if we have seasons data if (!seasonsData.seasons || !Array.isArray(seasonsData.seasons) || seasonsData.seasons.length === 0) { formattedSeasons.push("No seasons data available for this team."); return { content: [ { type: "text", text: formattedSeasons.join("\n"), }, ], }; } // Add table header formattedSeasons.push("| Season | Game Types Available |"); formattedSeasons.push("|--------|----------------------|"); // Add each season seasonsData.seasons.forEach((season: any) => { const seasonCode = season.season || "N/A"; const seasonFormatted = `${seasonCode.substring(0, 4)}-${seasonCode.substring(4)}`; // Format game types let gameTypes = "None"; if (season.gameTypes && Array.isArray(season.gameTypes) && season.gameTypes.length > 0) { gameTypes = season.gameTypes.map((gt: any) => { const gameTypeId = gt.gameTypeId; // Map game type IDs to readable names switch (gameTypeId) { case 1: return "Preseason"; case 2: return "Regular Season"; case 3: return "Playoffs"; case 4: return "All-Star"; default: return `Type ${gameTypeId}`; } }).join(", "); } formattedSeasons.push(`| ${seasonFormatted} | ${gameTypes} |`); }); return { content: [ { type: "text", text: formattedSeasons.join("\n"), }, ], }; } ); server.tool( "get-team-stats-by-season", "Get statistics for an NHL team for a specific season and game type", { teamAbbrev: z.string().length(3).describe("Three-letter team abbreviation (e.g. TOR, NYR, BOS)"), season: z.string().describe("Season in YYYYYYYY format (e.g. 20232024)"), gameType: z.number().default(2).describe("Game type (2 for regular season, 3 for playoffs)"), }, async ({ teamAbbrev, season, gameType = 2 }: { teamAbbrev: string; season: string; gameType?: number }) => { console.error(`Getting stats for team: ${teamAbbrev}, season: ${season}, gameType: ${gameType}`); // Fetch team stats data from NHL API const statsUrl = `${NHL_WEB_API_BASE}/club-stats/${teamAbbrev}/${season}/${gameType}`; let statsData = null; try { console.error(`Fetching team stats from: ${statsUrl}`); statsData = await makeNHLRequest<any>(statsUrl); if (!statsData) { return { content: [ { type: "text", text: `Failed to retrieve stats for team: ${teamAbbrev}, season: ${season}, gameType: ${gameType}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching team stats: ${error}`); return { content: [ { type: "text", text: `Error retrieving stats for team: ${teamAbbrev}, season: ${season}, gameType: ${gameType}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Format the team stats data const formattedStats = []; // Add team header const seasonFormatted = `${season.substring(0, 4)}-${season.substring(4)}`; const gameTypeText = gameType === 2 ? "Regular Season" : gameType === 3 ? "Playoffs" : `Game Type ${gameType}`; formattedStats.push(`# ${teamAbbrev} Team Statistics - ${seasonFormatted} ${gameTypeText}\n`); // Add team record if available if (statsData.wins !== undefined && statsData.losses !== undefined && statsData.otLosses !== undefined) { const points = statsData.points !== undefined ? statsData.points : (statsData.wins * 2 + statsData.otLosses); formattedStats.push(`Record: ${statsData.wins}-${statsData.losses}-${statsData.otLosses} (${points} points)\n`); } // Add skater stats section if (statsData.skaters && statsData.skaters.length > 0) { formattedStats.push(`## Skaters (${statsData.skaters.length})\n`); // Add table header for skaters formattedStats.push("| Player | Pos | GP | G | A | P | +/- | PIM | PPG | SHG | GWG | S | S% |"); formattedStats.push("|--------|-----|----|----|----|----|-----|-----|-----|-----|-----|----|----|"); // Sort skaters by points (descending) const sortedSkaters = [...statsData.skaters].sort((a, b) => (b.points || 0) - (a.points || 0)); // Add each skater sortedSkaters.forEach((skater) => { const firstName = safeString(skater.firstName?.default, ""); const lastName = safeString(skater.lastName?.default, ""); const name = `${firstName} ${lastName}`.trim(); const position = safeString(skater.positionCode, ""); const gamesPlayed = skater.gamesPlayed !== undefined ? skater.gamesPlayed : "0"; const goals = skater.goals !== undefined ? skater.goals : "0"; const assists = skater.assists !== undefined ? skater.assists : "0"; const points = skater.points !== undefined ? skater.points : "0"; const plusMinus = skater.plusMinus !== undefined ? (skater.plusMinus > 0 ? `+${skater.plusMinus}` : skater.plusMinus) : "0"; const pim = skater.penaltyMinutes !== undefined ? skater.penaltyMinutes : "0"; const ppg = skater.powerPlayGoals !== undefined ? skater.powerPlayGoals : "0"; const shg = skater.shorthandedGoals !== undefined ? skater.shorthandedGoals : "0"; const gwg = skater.gameWinningGoals !== undefined ? skater.gameWinningGoals : "0"; const shots = skater.shots !== undefined ? skater.shots : "0"; const shootingPct = skater.shootingPctg !== undefined ? skater.shootingPctg.toFixed(1) : "0.0"; formattedStats.push(`| ${name} | ${position} | ${gamesPlayed} | ${goals} | ${assists} | ${points} | ${plusMinus} | ${pim} | ${ppg} | ${shg} | ${gwg} | ${shots} | ${shootingPct} |`); }); formattedStats.push(""); } // Add goalie stats section if (statsData.goalies && statsData.goalies.length > 0) { formattedStats.push(`## Goalies (${statsData.goalies.length})\n`); // Add table header for goalies formattedStats.push("| Goalie | GP | GS | W | L | OTL | GAA | SV% | SO |"); formattedStats.push("|--------|----|----|---|---|-----|-----|-----|---|"); // Sort goalies by games played (descending) const sortedGoalies = [...statsData.goalies].sort((a, b) => (b.gamesPlayed || 0) - (a.gamesPlayed || 0)); // Add each goalie sortedGoalies.forEach((goalie) => { const firstName = safeString(goalie.firstName?.default, ""); const lastName = safeString(goalie.lastName?.default, ""); const name = `${firstName} ${lastName}`.trim(); const gamesPlayed = goalie.gamesPlayed !== undefined ? goalie.gamesPlayed : "0"; const gamesStarted = goalie.gamesStarted !== undefined ? goalie.gamesStarted : "0"; const wins = goalie.wins !== undefined ? goalie.wins : "0"; const losses = goalie.losses !== undefined ? goalie.losses : "0"; const otLosses = goalie.overtimeLosses !== undefined ? goalie.overtimeLosses : "0"; const gaa = goalie.goalsAgainstAverage !== undefined ? goalie.goalsAgainstAverage.toFixed(2) : "0.00"; const svPct = goalie.savePercentage !== undefined ? goalie.savePercentage.toFixed(3) : "0.000"; const shutouts = goalie.shutouts !== undefined ? goalie.shutouts : "0"; formattedStats.push(`| ${name} | ${gamesPlayed} | ${gamesStarted} | ${wins} | ${losses} | ${otLosses} | ${gaa} | ${svPct} | ${shutouts} |`); }); } return { content: [ { type: "text", text: formattedStats.join("\n"), }, ], }; } ); server.tool( "get-roster-by-season", "Get an NHL team's roster for a specific season", { teamAbbrev: z.string().length(3).describe("Three-letter team abbreviation (e.g. TOR, NYR, BOS)"), season: z.string().describe("Season in YYYYYYYY format (e.g. 20232024)"), }, async ({ teamAbbrev, season }: { teamAbbrev: string; season: string }) => { console.error(`Getting roster for team: ${teamAbbrev}, season: ${season}`); // Fetch roster data from NHL API const rosterUrl = `${NHL_WEB_API_BASE}/roster/${teamAbbrev}/${season}`; let rosterData = null; try { console.error(`Fetching roster from: ${rosterUrl}`); rosterData = await makeNHLRequest<any>(rosterUrl); if (!rosterData) { return { content: [ { type: "text", text: `Failed to retrieve roster for team: ${teamAbbrev}, season: ${season}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching roster: ${error}`); return { content: [ { type: "text", text: `Error retrieving roster for team: ${teamAbbrev}, season: ${season}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Format the roster data const formattedRoster = []; const seasonFormatted = `${season.substring(0, 4)}-${season.substring(4)}`; // Add header formattedRoster.push(`# ${teamAbbrev} Roster - ${seasonFormatted} Season\n`); // Process forwards if (rosterData.forwards && rosterData.forwards.length > 0) { formattedRoster.push(`## Forwards (${rosterData.forwards.length})\n`); formattedRoster.push("| # | Player | Pos | Shoots | Height | Weight | Birth Date | Birthplace |"); formattedRoster.push("|---|--------|-----|--------|--------|--------|------------|------------|"); // Sort forwards by jersey number const sortedForwards = [...rosterData.forwards].sort((a, b) => (a.sweaterNumber || 999) - (b.sweaterNumber || 999)); sortedForwards.forEach(player => { const number = player.sweaterNumber || "-"; const firstName = safeString(player.firstName?.default, ""); const lastName = safeString(player.lastName?.default, ""); const name = `${firstName} ${lastName}`.trim(); const position = safeString(player.positionCode, ""); const shoots = player.shootsCatches === "L" ? "Left" : player.shootsCatches === "R" ? "Right" : player.shootsCatches || "-"; // Format height as feet and inches let height = "-"; if (player.heightInInches) { const feet = Math.floor(player.heightInInches / 12); const inches = player.heightInInches % 12; height = `${feet}'${inches}"`; } // Format weight const weight = player.weightInPounds ? `${player.weightInPounds} lbs` : "-"; // Format birth date let birthDate = "-"; if (player.birthDate) { const date = new Date(player.birthDate); birthDate = date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); } // Format birthplace const birthCity = safeString(player.birthCity, ""); const birthState = safeString(player.birthStateProvince, ""); const birthCountry = player.birthCountry || ""; let birthplace = ""; if (birthCity) { birthplace += birthCity; if (birthState) birthplace += `, ${birthState}`; if (birthCountry) birthplace += `, ${birthCountry}`; } else if (birthCountry) { birthplace = birthCountry; } else { birthplace = "-"; } formattedRoster.push(`| ${number} | ${name} | ${position} | ${shoots} | ${height} | ${weight} | ${birthDate} | ${birthplace} |`); }); formattedRoster.push(""); } // Process defensemen if (rosterData.defensemen && rosterData.defensemen.length > 0) { formattedRoster.push(`## Defensemen (${rosterData.defensemen.length})\n`); formattedRoster.push("| # | Player | Shoots | Height | Weight | Birth Date | Birthplace |"); formattedRoster.push("|---|--------|--------|--------|--------|------------|------------|"); // Sort defensemen by jersey number const sortedDefensemen = [...rosterData.defensemen].sort((a, b) => (a.sweaterNumber || 999) - (b.sweaterNumber || 999)); sortedDefensemen.forEach(player => { const number = player.sweaterNumber || "-"; const firstName = safeString(player.firstName?.default, ""); const lastName = safeString(player.lastName?.default, ""); const name = `${firstName} ${lastName}`.trim(); const shoots = player.shootsCatches === "L" ? "Left" : player.shootsCatches === "R" ? "Right" : player.shootsCatches || "-"; // Format height as feet and inches let height = "-"; if (player.heightInInches) { const feet = Math.floor(player.heightInInches / 12); const inches = player.heightInInches % 12; height = `${feet}'${inches}"`; } // Format weight const weight = player.weightInPounds ? `${player.weightInPounds} lbs` : "-"; // Format birth date let birthDate = "-"; if (player.birthDate) { const date = new Date(player.birthDate); birthDate = date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); } // Format birthplace const birthCity = safeString(player.birthCity, ""); const birthState = safeString(player.birthStateProvince, ""); const birthCountry = player.birthCountry || ""; let birthplace = ""; if (birthCity) { birthplace += birthCity; if (birthState) birthplace += `, ${birthState}`; if (birthCountry) birthplace += `, ${birthCountry}`; } else if (birthCountry) { birthplace = birthCountry; } else { birthplace = "-"; } formattedRoster.push(`| ${number} | ${name} | ${shoots} | ${height} | ${weight} | ${birthDate} | ${birthplace} |`); }); formattedRoster.push(""); } // Process goalies if (rosterData.goalies && rosterData.goalies.length > 0) { formattedRoster.push(`## Goalies (${rosterData.goalies.length})\n`); formattedRoster.push("| # | Goalie | Catches | Height | Weight | Birth Date | Birthplace |"); formattedRoster.push("|---|--------|---------|--------|--------|------------|------------|"); // Sort goalies by jersey number const sortedGoalies = [...rosterData.goalies].sort((a, b) => (a.sweaterNumber || 999) - (b.sweaterNumber || 999)); sortedGoalies.forEach(player => { const number = player.sweaterNumber || "-"; const firstName = safeString(player.firstName?.default, ""); const lastName = safeString(player.lastName?.default, ""); const name = `${firstName} ${lastName}`.trim(); const catches = player.shootsCatches === "L" ? "Left" : player.shootsCatches === "R" ? "Right" : player.shootsCatches || "-"; // Format height as feet and inches let height = "-"; if (player.heightInInches) { const feet = Math.floor(player.heightInInches / 12); const inches = player.heightInInches % 12; height = `${feet}'${inches}"`; } // Format weight const weight = player.weightInPounds ? `${player.weightInPounds} lbs` : "-"; // Format birth date let birthDate = "-"; if (player.birthDate) { const date = new Date(player.birthDate); birthDate = date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); } // Format birthplace const birthCity = safeString(player.birthCity, ""); const birthState = safeString(player.birthStateProvince, ""); const birthCountry = player.birthCountry || ""; let birthplace = ""; if (birthCity) { birthplace += birthCity; if (birthState) birthplace += `, ${birthState}`; if (birthCountry) birthplace += `, ${birthCountry}`; } else if (birthCountry) { birthplace = birthCountry; } else { birthplace = "-"; } formattedRoster.push(`| ${number} | ${name} | ${catches} | ${height} | ${weight} | ${birthDate} | ${birthplace} |`); }); } // Add roster summary const totalPlayers = (rosterData.forwards?.length || 0) + (rosterData.defensemen?.length || 0) + (rosterData.goalies?.length || 0); formattedRoster.push(`\n**Total Players:** ${totalPlayers}`); return { content: [ { type: "text", text: formattedRoster.join("\n"), }, ], }; } ); server.tool( "get-team-prospects", "Get prospects for an NHL team", { teamAbbrev: z.string().length(3).describe("Three-letter team abbreviation (e.g. TOR, NYR, BOS)"), }, async ({ teamAbbrev }: { teamAbbrev: string }) => { console.error(`Getting prospects for team: ${teamAbbrev}`); // Fetch prospects data from NHL API const prospectsUrl = `${NHL_WEB_API_BASE}/prospects/${teamAbbrev}`; let prospectsData = null; try { console.error(`Fetching prospects from: ${prospectsUrl}`); prospectsData = await makeNHLRequest<any>(prospectsUrl); if (!prospectsData) { return { content: [ { type: "text", text: `Failed to retrieve prospects for team: ${teamAbbrev}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching prospects: ${error}`); return { content: [ { type: "text", text: `Error retrieving prospects for team: ${teamAbbrev}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Format the prospects data const formattedProspects = []; // Add header formattedProspects.push(`# ${teamAbbrev} Prospects\n`); // Process forwards if (prospectsData.forwards && prospectsData.forwards.length > 0) { formattedProspects.push(`## Forward Prospects (${prospectsData.forwards.length})\n`); formattedProspects.push("| Player | Pos | Age | Shoots | Height | Weight | Birthplace |"); formattedProspects.push("|--------|-----|-----|--------|--------|--------|------------|"); // Sort forwards alphabetically by last name const sortedForwards = [...prospectsData.forwards].sort((a, b) => { const lastNameA = safeString(a.lastName?.default, ""); const lastNameB = safeString(b.lastName?.default, ""); return lastNameA.localeCompare(lastNameB); }); sortedForwards.forEach(player => { const firstName = safeString(player.firstName?.default, ""); const lastName = safeString(player.lastName?.default, ""); const name = `${firstName} ${lastName}`.trim(); const position = safeString(player.positionCode, ""); const shoots = player.shootsCatches === "L" ? "Left" : player.shootsCatches === "R" ? "Right" : player.shootsCatches || "-"; // Calculate age let age = "-"; if (player.birthDate) { const birthDate = new Date(player.birthDate); const today = new Date(); let calculatedAge = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { calculatedAge--; } age = String(calculatedAge); } // Format height as feet and inches let height = "-"; if (player.heightInInches) { const feet = Math.floor(player.heightInInches / 12); const inches = player.heightInInches % 12; height = `${feet}'${inches}"`; } // Format weight const weight = player.weightInPounds ? `${player.weightInPounds} lbs` : "-"; // Format birthplace const birthCity = safeString(player.birthCity, ""); const birthState = safeString(player.birthStateProvince, ""); const birthCountry = player.birthCountry || ""; let birthplace = ""; if (birthCity) { birthplace += birthCity; if (birthState) birthplace += `, ${birthState}`; if (birthCountry) birthplace += `, ${birthCountry}`; } else if (birthCountry) { birthplace = birthCountry; } else { birthplace = "-"; } formattedProspects.push(`| ${name} | ${position} | ${age} | ${shoots} | ${height} | ${weight} | ${birthplace} |`); }); formattedProspects.push(""); } // Process defensemen if (prospectsData.defensemen && prospectsData.defensemen.length > 0) { formattedProspects.push(`## Defensive Prospects (${prospectsData.defensemen.length})\n`); formattedProspects.push("| Player | Age | Shoots | Height | Weight | Birthplace |"); formattedProspects.push("|--------|-----|--------|--------|--------|------------|"); // Sort defensemen alphabetically by last name const sortedDefensemen = [...prospectsData.defensemen].sort((a, b) => { const lastNameA = safeString(a.lastName?.default, ""); const lastNameB = safeString(b.lastName?.default, ""); return lastNameA.localeCompare(lastNameB); }); sortedDefensemen.forEach(player => { const firstName = safeString(player.firstName?.default, ""); const lastName = safeString(player.lastName?.default, ""); const name = `${firstName} ${lastName}`.trim(); const shoots = player.shootsCatches === "L" ? "Left" : player.shootsCatches === "R" ? "Right" : player.shootsCatches || "-"; // Calculate age let age = "-"; if (player.birthDate) { const birthDate = new Date(player.birthDate); const today = new Date(); let calculatedAge = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { calculatedAge--; } age = String(calculatedAge); } // Format height as feet and inches let height = "-"; if (player.heightInInches) { const feet = Math.floor(player.heightInInches / 12); const inches = player.heightInInches % 12; height = `${feet}'${inches}"`; } // Format weight const weight = player.weightInPounds ? `${player.weightInPounds} lbs` : "-"; // Format birthplace const birthCity = safeString(player.birthCity, ""); const birthState = safeString(player.birthStateProvince, ""); const birthCountry = player.birthCountry || ""; let birthplace = ""; if (birthCity) { birthplace += birthCity; if (birthState) birthplace += `, ${birthState}`; if (birthCountry) birthplace += `, ${birthCountry}`; } else if (birthCountry) { birthplace = birthCountry; } else { birthplace = "-"; } formattedProspects.push(`| ${name} | ${age} | ${shoots} | ${height} | ${weight} | ${birthplace} |`); }); formattedProspects.push(""); } // Process goalies if (prospectsData.goalies && prospectsData.goalies.length > 0) { formattedProspects.push(`## Goalie Prospects (${prospectsData.goalies.length})\n`); formattedProspects.push("| Goalie | Age | Catches | Height | Weight | Birthplace |"); formattedProspects.push("|--------|-----|---------|--------|--------|------------|"); // Sort goalies alphabetically by last name const sortedGoalies = [...prospectsData.goalies].sort((a, b) => { const lastNameA = safeString(a.lastName?.default, ""); const lastNameB = safeString(b.lastName?.default, ""); return lastNameA.localeCompare(lastNameB); }); sortedGoalies.forEach(player => { const firstName = safeString(player.firstName?.default, ""); const lastName = safeString(player.lastName?.default, ""); const name = `${firstName} ${lastName}`.trim(); const catches = player.shootsCatches === "L" ? "Left" : player.shootsCatches === "R" ? "Right" : player.shootsCatches || "-"; // Calculate age let age = "-"; if (player.birthDate) { const birthDate = new Date(player.birthDate); const today = new Date(); let calculatedAge = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { calculatedAge--; } age = String(calculatedAge); } // Format height as feet and inches let height = "-"; if (player.heightInInches) { const feet = Math.floor(player.heightInInches / 12); const inches = player.heightInInches % 12; height = `${feet}'${inches}"`; } // Format weight const weight = player.weightInPounds ? `${player.weightInPounds} lbs` : "-"; // Format birthplace const birthCity = safeString(player.birthCity, ""); const birthState = safeString(player.birthStateProvince, ""); const birthCountry = player.birthCountry || ""; let birthplace = ""; if (birthCity) { birthplace += birthCity; if (birthState) birthplace += `, ${birthState}`; if (birthCountry) birthplace += `, ${birthCountry}`; } else if (birthCountry) { birthplace = birthCountry; } else { birthplace = "-"; } formattedProspects.push(`| ${name} | ${age} | ${catches} | ${height} | ${weight} | ${birthplace} |`); }); } // Add prospects summary const totalProspects = (prospectsData.forwards?.length || 0) + (prospectsData.defensemen?.length || 0) + (prospectsData.goalies?.length || 0); formattedProspects.push(`\n**Total Prospects:** ${totalProspects}`); // Add note about what prospects are formattedProspects.push("\n*Note: Prospects typically include drafted players and young players in development systems who have not yet established themselves as regular NHL players.*"); return { content: [ { type: "text", text: formattedProspects.join("\n"), }, ], }; } ); server.tool( "get-current-schedule", "Get the current NHL schedule for a specific team", { teamAbbrev: z.string().length(3).describe("Three-letter team abbreviation (e.g. TOR, NYR, BOS)"), limit: z.number().optional().describe("Number of games to return (default: 10)"), }, async ({ teamAbbrev, limit = 10 }: { teamAbbrev: string; limit?: number }) => { console.error(`Getting current schedule for team: ${teamAbbrev}, limit: ${limit}`); const teamCode = teamAbbrev.toUpperCase(); // Fetch schedule data from NHL API const scheduleUrl = `${NHL_WEB_API_BASE}/club-schedule/${teamCode}/week/now`; let scheduleData = null; try { console.error(`Fetching schedule from: ${scheduleUrl}`); scheduleData = await makeNHLRequest<any>(scheduleUrl); if (!scheduleData) { return { content: [ { type: "text", text: `Failed to retrieve schedule for team: ${teamCode}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching schedule: ${error}`); return { content: [ { type: "text", text: `Error retrieving schedule for team: ${teamCode}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Format the schedule data const formattedSchedule = []; // Add header formattedSchedule.push(`# ${teamCode} Schedule\n`); // Check if we have games data if (!scheduleData.games || !Array.isArray(scheduleData.games) || scheduleData.games.length === 0) { formattedSchedule.push("No upcoming games found for this team."); } else { // Get current and previous season info const currentSeason = scheduleData.currentSeason || "N/A"; const previousSeason = scheduleData.previousSeason || "N/A"; formattedSchedule.push(`Current Season: ${currentSeason}, Previous Season: ${previousSeason}\n`); // Add table header formattedSchedule.push("| Date | Opponent | Location | Time (ET) | Result |"); formattedSchedule.push("|------|----------|----------|-----------|--------|"); // Add each game (limited by the limit parameter) const games = scheduleData.games.slice(0, limit); games.forEach((game: any) => { // Determine if team is home or away const isHome = game.homeTeam && game.homeTeam.abbrev === teamCode; const opponent = isHome ? game.awayTeam : game.homeTeam; const opponentCode = opponent ? safeString(opponent.abbrev) : "N/A"; // Format date const gameDate = game.gameDate ? new Date(game.gameDate).toLocaleDateString() : "N/A"; // Format time let gameTime = "N/A"; if (game.startTimeUTC) { try { const date = new Date(game.startTimeUTC); gameTime = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } catch (e) { gameTime = "N/A"; } } // Format location const location = isHome ? "Home" : "Away"; // Format result let result = "Upcoming"; if (game.gameState === "FINAL") { const teamScore = isHome ? game.homeTeam.score : game.awayTeam.score; const opponentScore = isHome ? game.awayTeam.score : game.homeTeam.score; result = teamScore > opponentScore ? `W ${teamScore}-${opponentScore}` : `L ${teamScore}-${opponentScore}`; // Check for OT or SO if (game.periodDescriptor && game.periodDescriptor.periodType === "OT") { result += " (OT)"; } else if (game.periodDescriptor && game.periodDescriptor.periodType === "SO") { result += " (SO)"; } } else if (game.gameState === "LIVE") { const teamScore = isHome ? game.homeTeam.score : game.awayTeam.score; const opponentScore = isHome ? game.awayTeam.score : game.homeTeam.score; result = `${teamScore}-${opponentScore} (Live)`; } formattedSchedule.push(`| ${gameDate} | ${opponentCode} | ${location} | ${gameTime} | ${result} |`); }); } return { content: [ { type: "text", text: formattedSchedule.join("\n"), }, ], }; } ); server.tool( "get-week-schedule", "Get the NHL schedule for a specific team for a given week", { teamAbbrev: z.string().length(3).describe("Three-letter team abbreviation (e.g. TOR, NYR, BOS)"), date: z.string().describe("Date in YYYY-MM-DD format to get the week containing this date"), }, async ({ teamAbbrev, date }: { teamAbbrev: string; date: string }) => { console.error(`Getting week schedule for team: ${teamAbbrev}, date: ${date}`); const teamCode = teamAbbrev.toUpperCase(); // Validate date format if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) { return { content: [ { type: "text", text: `Invalid date format. Please use YYYY-MM-DD format (e.g. 2025-01-15).`, }, ], }; } // Fetch schedule data from NHL API const scheduleUrl = `${NHL_WEB_API_BASE}/club-schedule/${teamCode}/week/${date}`; let scheduleData = null; try { console.error(`Fetching week schedule from: ${scheduleUrl}`); scheduleData = await makeNHLRequest<any>(scheduleUrl); if (!scheduleData) { return { content: [ { type: "text", text: `Failed to retrieve week schedule for team: ${teamCode}, date: ${date}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching week schedule: ${error}`); return { content: [ { type: "text", text: `Error retrieving week schedule for team: ${teamCode}, date: ${date}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Format the schedule data const formattedSchedule = []; // Add header formattedSchedule.push(`# ${teamCode} Week Schedule (${date})\n`); // Check if we have games data if (!scheduleData.games || !Array.isArray(scheduleData.games) || scheduleData.games.length === 0) { formattedSchedule.push("No games found for this team during the specified week."); } else { // Get date range for the week const firstGame = scheduleData.games[0]; const lastGame = scheduleData.games[scheduleData.games.length - 1]; let weekRange = ""; if (firstGame && firstGame.gameDate && lastGame && lastGame.gameDate) { const firstDate = new Date(firstGame.gameDate).toLocaleDateString(); const lastDate = new Date(lastGame.gameDate).toLocaleDateString(); weekRange = `${firstDate} to ${lastDate}`; } if (weekRange) { formattedSchedule.push(`Week: ${weekRange}\n`); } // Add table header formattedSchedule.push("| Date | Opponent | Location | Time (ET) | Result |"); formattedSchedule.push("|------|----------|----------|-----------|--------|"); // Add each game scheduleData.games.forEach((game: any) => { // Determine if team is home or away const isHome = game.homeTeam && game.homeTeam.abbrev === teamCode; const opponent = isHome ? game.awayTeam : game.homeTeam; const opponentCode = opponent ? safeString(opponent.abbrev) : "N/A"; // Format date const gameDate = game.gameDate ? new Date(game.gameDate).toLocaleDateString() : "N/A"; // Format time let gameTime = "N/A"; if (game.startTimeUTC) { try { const date = new Date(game.startTimeUTC); gameTime = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } catch (e) { gameTime = "N/A"; } } // Format location const location = isHome ? "Home" : "Away"; // Format result let result = "Upcoming"; if (game.gameState === "FINAL" || game.gameState === "OFF") { const teamScore = isHome ? game.homeTeam.score : game.awayTeam.score; const opponentScore = isHome ? game.awayTeam.score : game.homeTeam.score; result = teamScore > opponentScore ? `W ${teamScore}-${opponentScore}` : `L ${teamScore}-${opponentScore}`; // Check for OT or SO if (game.periodDescriptor && game.periodDescriptor.periodType === "OT") { result += " (OT)"; } else if (game.periodDescriptor && game.periodDescriptor.periodType === "SO") { result += " (SO)"; } } else if (game.gameState === "LIVE") { const teamScore = isHome ? game.homeTeam.score : game.awayTeam.score; const opponentScore = isHome ? game.awayTeam.score : game.homeTeam.score; result = `${teamScore}-${opponentScore} (Live)`; } formattedSchedule.push(`| ${gameDate} | ${opponentCode} | ${location} | ${gameTime} | ${result} |`); }); // Add summary formattedSchedule.push(`\nTotal Games This Week: ${scheduleData.games.length}`); } return { content: [ { type: "text", text: formattedSchedule.join("\n"), }, ], }; } ); server.tool( "get-month-schedule", "Get the NHL schedule for a specific team for a given month", { teamAbbrev: z.string().length(3).describe("Three-letter team abbreviation (e.g. TOR, NYR, BOS)"), year: z.number().describe("Year (e.g. 2025)"), month: z.number().min(1).max(12).describe("Month (1-12)"), }, async ({ teamAbbrev, year, month }: { teamAbbrev: string; year: number; month: number }) => { console.error(`Getting month schedule for team: ${teamAbbrev}, year: ${year}, month: ${month}`); const teamCode = teamAbbrev.toUpperCase(); // Validate month if (month < 1 || month > 12) { return { content: [ { type: "text", text: `Invalid month. Please provide a month between 1 and 12.`, }, ], }; } // Format month for API (zero-padded) const monthFormatted = month.toString().padStart(2, '0'); // Fetch schedule data from NHL API const scheduleUrl = `${NHL_WEB_API_BASE}/club-schedule/${teamCode}/month/${year}-${monthFormatted}-01`; let scheduleData = null; try { console.error(`Fetching month schedule from: ${scheduleUrl}`); scheduleData = await makeNHLRequest<any>(scheduleUrl); if (!scheduleData) { return { content: [ { type: "text", text: `Failed to retrieve month schedule for team: ${teamCode}, year: ${year}, month: ${month}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching month schedule: ${error}`); return { content: [ { type: "text", text: `Error retrieving month schedule for team: ${teamCode}, year: ${year}, month: ${month}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Format the schedule data const formattedSchedule = []; // Get month name const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; const monthName = monthNames[month - 1]; // Add header formattedSchedule.push(`# ${teamCode} Schedule - ${monthName} ${year}\n`); // Check if we have games data if (!scheduleData.games || !Array.isArray(scheduleData.games) || scheduleData.games.length === 0) { formattedSchedule.push("No games found for this team during the specified month."); } else { // Add table header formattedSchedule.push("| Date | Opponent | Location | Time (ET) | Result |"); formattedSchedule.push("|------|----------|----------|-----------|--------|"); // Add each game scheduleData.games.forEach((game: any) => { // Determine if team is home or away const isHome = game.homeTeam && game.homeTeam.abbrev === teamCode; const opponent = isHome ? game.awayTeam : game.homeTeam; const opponentCode = opponent ? safeString(opponent.abbrev) : "N/A"; // Format date const gameDate = game.gameDate ? new Date(game.gameDate).toLocaleDateString() : "N/A"; // Format time let gameTime = "N/A"; if (game.startTimeUTC) { try { const date = new Date(game.startTimeUTC); gameTime = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } catch (e) { gameTime = "N/A"; } } // Format location const location = isHome ? "Home" : "Away"; // Format result let result = "Upcoming"; if (game.gameState === "FINAL" || game.gameState === "OFF") { const teamScore = isHome ? game.homeTeam.score : game.awayTeam.score; const opponentScore = isHome ? game.awayTeam.score : game.homeTeam.score; result = teamScore > opponentScore ? `W ${teamScore}-${opponentScore}` : `L ${teamScore}-${opponentScore}`; // Check for OT or SO if (game.periodDescriptor && game.periodDescriptor.periodType === "OT") { result += " (OT)"; } else if (game.periodDescriptor && game.periodDescriptor.periodType === "SO") { result += " (SO)"; } } else if (game.gameState === "LIVE") { const teamScore = isHome ? game.homeTeam.score : game.awayTeam.score; const opponentScore = isHome ? game.awayTeam.score : game.homeTeam.score; result = `${teamScore}-${opponentScore} (Live)`; } formattedSchedule.push(`| ${gameDate} | ${opponentCode} | ${location} | ${gameTime} | ${result} |`); }); // Add summary formattedSchedule.push(`\nTotal Games in ${monthName} ${year}: ${scheduleData.games.length}`); // Add home/away breakdown const homeGames = scheduleData.games.filter((game: any) => game.homeTeam && game.homeTeam.abbrev === teamCode).length; const awayGames = scheduleData.games.length - homeGames; formattedSchedule.push(`Home Games: ${homeGames}, Away Games: ${awayGames}`); } return { content: [ { type: "text", text: formattedSchedule.join("\n"), }, ], }; } ); server.tool( "get-season-schedule", "Get the full season schedule for a specific team", { teamAbbrev: z.string().length(3).describe("Three-letter team abbreviation (e.g. TOR, NYR, BOS)"), season: z.string().describe("Season code (e.g. 20242025 for the 2024-2025 season)"), gameType: z.number().optional().default(2).describe("Game type (1 for preseason, 2 for regular season, 3 for playoffs)"), }, async ({ teamAbbrev, season, gameType = 2 }: { teamAbbrev: string; season: string; gameType?: number }) => { console.error(`Getting season schedule for team: ${teamAbbrev}, season: ${season}, gameType: ${gameType}`); const teamCode = teamAbbrev.toUpperCase(); // Fetch schedule data from NHL API const scheduleUrl = `${NHL_WEB_API_BASE}/club-schedule/${teamCode}/season/${season}`; let scheduleData = null; try { console.error(`Fetching schedule from: ${scheduleUrl}`); scheduleData = await makeNHLRequest<any>(scheduleUrl); if (!scheduleData) { return { content: [ { type: "text", text: `Failed to retrieve season schedule for team: ${teamCode}, season: ${season}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching season schedule: ${error}`); return { content: [ { type: "text", text: `Error retrieving season schedule for team: ${teamCode}, season: ${season}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Format the schedule data const formattedSchedule = []; // Add header formattedSchedule.push(`# ${teamCode} ${season} Season Schedule\n`); // Check if we have games data if (!scheduleData.games || !Array.isArray(scheduleData.games) || scheduleData.games.length === 0) { formattedSchedule.push("No games found for this team and season."); } else { // Filter games by game type if specified const filteredGames = scheduleData.games.filter((game: any) => gameType === undefined || game.gameType === gameType ); if (filteredGames.length === 0) { formattedSchedule.push(`No games found for game type: ${gameType}`); } else { // Add game type description let gameTypeDesc = "Regular Season"; if (gameType === 1) gameTypeDesc = "Preseason"; if (gameType === 3) gameTypeDesc = "Playoffs"; formattedSchedule.push(`## ${gameTypeDesc} Games\n`); // Add table header formattedSchedule.push("| Date | Opponent | Location | Time (ET) | Result |"); formattedSchedule.push("|------|----------|----------|-----------|--------|"); // Add each game filteredGames.forEach((game: any) => { // Determine if team is home or away const isHome = game.homeTeam && game.homeTeam.abbrev === teamCode; const opponent = isHome ? game.awayTeam : game.homeTeam; const opponentCode = opponent ? safeString(opponent.abbrev) : "N/A"; // Format date const gameDate = game.gameDate ? new Date(game.gameDate).toLocaleDateString() : "N/A"; // Format time let gameTime = "N/A"; if (game.startTimeUTC) { try { const date = new Date(game.startTimeUTC); gameTime = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } catch (e) { gameTime = "N/A"; } } // Format location const location = isHome ? "Home" : "Away"; // Format result let result = "Upcoming"; if (game.gameState === "FINAL" || game.gameState === "OFF") { const teamScore = isHome ? game.homeTeam.score : game.awayTeam.score; const opponentScore = isHome ? game.awayTeam.score : game.homeTeam.score; result = teamScore > opponentScore ? `W ${teamScore}-${opponentScore}` : `L ${teamScore}-${opponentScore}`; // Check for OT or SO if (game.periodDescriptor && game.periodDescriptor.periodType === "OT") { result += " (OT)"; } else if (game.periodDescriptor && game.periodDescriptor.periodType === "SO") { result += " (SO)"; } } else if (game.gameState === "LIVE") { const teamScore = isHome ? game.homeTeam.score : game.awayTeam.score; const opponentScore = isHome ? game.awayTeam.score : game.homeTeam.score; result = `${teamScore}-${opponentScore} (Live)`; } formattedSchedule.push(`| ${gameDate} | ${opponentCode} | ${location} | ${gameTime} | ${result} |`); }); // Add summary formattedSchedule.push(`\nTotal ${gameTypeDesc} Games: ${filteredGames.length}`); } } return { content: [ { type: "text", text: formattedSchedule.join("\n"), }, ], }; } ); server.tool( "get-date-schedule", "Get the NHL schedule for a specific date", { date: z.string().describe("Date in YYYY-MM-DD format"), }, async ({ date }: { date: string }) => { console.error(`Getting NHL schedule for date: ${date}`); // Validate date format (YYYY-MM-DD) const dateRegex = /^\d{4}-\d{2}-\d{2}$/; if (!dateRegex.test(date)) { return { content: [ { type: "text", text: `Invalid date format. Please provide the date in YYYY-MM-DD format.`, }, ], }; } // Fetch schedule data from NHL API const scheduleUrl = `${NHL_WEB_API_BASE}/schedule/${date}`; let scheduleData = null; try { console.error(`Fetching schedule from: ${scheduleUrl}`); scheduleData = await makeNHLRequest<any>(scheduleUrl); if (!scheduleData) { return { content: [ { type: "text", text: `Failed to retrieve NHL schedule for date: ${date}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching schedule: ${error}`); return { content: [ { type: "text", text: `Error retrieving NHL schedule for date: ${date}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Format the schedule data const formattedSchedule = []; // Add header formattedSchedule.push(`# NHL Schedule - ${date}\n`); // Check if we have game week data if (!scheduleData.gameWeek || !Array.isArray(scheduleData.gameWeek) || scheduleData.gameWeek.length === 0) { formattedSchedule.push("No games scheduled for this date."); } else { // Process each day in the game week scheduleData.gameWeek.forEach((day: any) => { if (day.date === date) { formattedSchedule.push(`## ${day.date} (${day.dayAbbrev}) - ${day.numberOfGames} games\n`); if (!day.games || day.games.length === 0) { formattedSchedule.push("No games scheduled for this date."); return; } // Add table header formattedSchedule.push("| Time (ET) | Away Team | Home Team | Status | Score |"); formattedSchedule.push("|-----------|-----------|-----------|--------|-------|"); // Add each game day.games.forEach((game: any) => { // Format time let gameTime = "TBD"; if (game.startTimeUTC) { try { const date = new Date(game.startTimeUTC); gameTime = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } catch (e) { gameTime = "TBD"; } } // Get team info const awayTeam = game.awayTeam ? safeString(game.awayTeam.abbrev) : "N/A"; const homeTeam = game.homeTeam ? safeString(game.homeTeam.abbrev) : "N/A"; // Format status let status = "Scheduled"; if (game.gameState === "LIVE") { status = "Live"; if (game.periodDescriptor) { status += ` (${game.periodDescriptor.number}${game.periodDescriptor.periodType})`; } } else if (game.gameState === "FINAL" || game.gameState === "OFF") { status = "Final"; if (game.periodDescriptor && game.periodDescriptor.periodType === "OT") { status += " (OT)"; } else if (game.periodDescriptor && game.periodDescriptor.periodType === "SO") { status += " (SO)"; } } // Format score let score = "N/A"; if (game.gameState === "LIVE" || game.gameState === "FINAL" || game.gameState === "OFF") { const awayScore = game.awayTeam ? game.awayTeam.score : 0; const homeScore = game.homeTeam ? game.homeTeam.score : 0; score = `${awayScore} - ${homeScore}`; } formattedSchedule.push(`| ${gameTime} | ${awayTeam} | ${homeTeam} | ${status} | ${score} |`); }); // Add venue information if available day.games.forEach((game: any) => { if (game.venue) { const venue = safeString(game.venue); const awayTeam = game.awayTeam ? safeString(game.awayTeam.abbrev) : "N/A"; const homeTeam = game.homeTeam ? safeString(game.homeTeam.abbrev) : "N/A"; formattedSchedule.push(`\n${awayTeam} @ ${homeTeam} - Venue: ${venue}`); } }); } }); // If no matching date was found in the game week if (formattedSchedule.length === 1) { formattedSchedule.push("No games scheduled for this date."); } } return { content: [ { type: "text", text: formattedSchedule.join("\n"), }, ], }; } ); server.tool( "get-date-range-schedule", "Get the NHL schedule for a date range", { startDate: z.string().describe("Start date in YYYY-MM-DD format"), endDate: z.string().describe("End date in YYYY-MM-DD format"), }, async ({ startDate, endDate }: { startDate: string; endDate: string }) => { console.error(`Getting NHL schedule from ${startDate} to ${endDate}`); // Validate date format (YYYY-MM-DD) const dateRegex = /^\d{4}-\d{2}-\d{2}$/; if (!dateRegex.test(startDate) || !dateRegex.test(endDate)) { return { content: [ { type: "text", text: `Invalid date format. Please provide dates in YYYY-MM-DD format.`, }, ], }; } // Validate date range const start = new Date(startDate); const end = new Date(endDate); if (isNaN(start.getTime()) || isNaN(end.getTime())) { return { content: [ { type: "text", text: `Invalid date values. Please provide valid dates.`, }, ], }; } if (start > end) { return { content: [ { type: "text", text: `Start date must be before or equal to end date.`, }, ], }; } // Limit range to 14 days to prevent excessive API calls const dayDiff = Math.floor((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)) + 1; if (dayDiff > 14) { return { content: [ { type: "text", text: `Date range too large. Please limit to 14 days or less.`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Format the schedule data const formattedSchedule = []; // Add header formattedSchedule.push(`# NHL Schedule - ${startDate} to ${endDate}\n`); // Generate array of dates in the range const dates = []; const currentDate = new Date(start); while (currentDate <= end) { dates.push(currentDate.toISOString().split('T')[0]); currentDate.setDate(currentDate.getDate() + 1); } // Fetch and process schedule for each date let totalGames = 0; for (const date of dates) { // Fetch schedule data from NHL API const scheduleUrl = `${NHL_WEB_API_BASE}/schedule/${date}`; let scheduleData = null; try { console.error(`Fetching schedule for date: ${date}`); scheduleData = await makeNHLRequest<any>(scheduleUrl); if (!scheduleData || !scheduleData.gameWeek) { formattedSchedule.push(`## ${date} - Failed to retrieve schedule data`); continue; } } catch (error) { console.error(`Error fetching schedule for date ${date}: ${error}`); formattedSchedule.push(`## ${date} - Error retrieving schedule data: ${error}`); continue; } // Process each day in the game week let foundMatchingDay = false; scheduleData.gameWeek.forEach((day: any) => { if (day.date === date) { foundMatchingDay = true; const numberOfGames = day.numberOfGames || 0; totalGames += numberOfGames; formattedSchedule.push(`## ${day.date} (${day.dayAbbrev}) - ${numberOfGames} games\n`); if (!day.games || day.games.length === 0) { formattedSchedule.push("No games scheduled for this date.\n"); return; } // Add table header formattedSchedule.push("| Time (ET) | Away Team | Home Team | Status | Score |"); formattedSchedule.push("|-----------|-----------|-----------|--------|-------|"); // Add each game day.games.forEach((game: any) => { // Format time let gameTime = "TBD"; if (game.startTimeUTC) { try { const date = new Date(game.startTimeUTC); gameTime = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } catch (e) { gameTime = "TBD"; } } // Get team info const awayTeam = game.awayTeam ? safeString(game.awayTeam.abbrev) : "N/A"; const homeTeam = game.homeTeam ? safeString(game.homeTeam.abbrev) : "N/A"; // Format status let status = "Scheduled"; if (game.gameState === "LIVE") { status = "Live"; if (game.periodDescriptor) { status += ` (${game.periodDescriptor.number}${game.periodDescriptor.periodType})`; } } else if (game.gameState === "FINAL" || game.gameState === "OFF") { status = "Final"; if (game.periodDescriptor && game.periodDescriptor.periodType === "OT") { status += " (OT)"; } else if (game.periodDescriptor && game.periodDescriptor.periodType === "SO") { status += " (SO)"; } } // Format score let score = "N/A"; if (game.gameState === "LIVE" || game.gameState === "FINAL" || game.gameState === "OFF") { const awayScore = game.awayTeam ? game.awayTeam.score : 0; const homeScore = game.homeTeam ? game.homeTeam.score : 0; score = `${awayScore} - ${homeScore}`; } formattedSchedule.push(`| ${gameTime} | ${awayTeam} | ${homeTeam} | ${status} | ${score} |`); }); formattedSchedule.push(""); } }); if (!foundMatchingDay) { formattedSchedule.push(`## ${date} - No games scheduled\n`); } } // Add summary formattedSchedule.push(`\n## Summary`); formattedSchedule.push(`Total Games in Range: ${totalGames}`); formattedSchedule.push(`Date Range: ${startDate} to ${endDate} (${dayDiff} days)`); return { content: [ { type: "text", text: formattedSchedule.join("\n"), }, ], }; } ); server.tool( "get-scores-now", "Get current NHL scores", { includeCompleted: z.boolean().optional().default(true).describe("Include completed games"), includeUpcoming: z.boolean().optional().default(true).describe("Include upcoming games"), }, async ({ includeCompleted = true, includeUpcoming = true }: { includeCompleted?: boolean; includeUpcoming?: boolean }) => { console.error(`Getting current NHL scores (includeCompleted: ${includeCompleted}, includeUpcoming: ${includeUpcoming})`); // Fetch schedule data from NHL API const scheduleUrl = `${NHL_WEB_API_BASE}/schedule/now`; let scheduleData = null; try { console.error(`Fetching scores from: ${scheduleUrl}`); scheduleData = await makeNHLRequest<any>(scheduleUrl); if (!scheduleData) { return { content: [ { type: "text", text: `Failed to retrieve NHL scores. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching scores: ${error}`); return { content: [ { type: "text", text: `Error retrieving NHL scores: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Format the scores data const formattedScores = []; // Add header formattedScores.push(`# NHL Scores - ${new Date().toLocaleDateString()}\n`); // Check if we have game data if (!scheduleData.gameWeek || !Array.isArray(scheduleData.gameWeek) || scheduleData.gameWeek.length === 0) { formattedScores.push("No games scheduled for today."); } else { // Process each day in the game week let totalGames = 0; let liveGames = 0; let completedGames = 0; let upcomingGames = 0; scheduleData.gameWeek.forEach((day: any) => { if (!day.games || day.games.length === 0) { return; } const currentDate = new Date().toISOString().split('T')[0]; if (day.date === currentDate) { formattedScores.push(`## ${day.date} (${day.dayAbbrev}) - ${day.numberOfGames} games\n`); // Filter games based on parameters let filteredGames = day.games; if (!includeCompleted) { filteredGames = filteredGames.filter((game: any) => game.gameState !== "FINAL" && game.gameState !== "OFF" ); } if (!includeUpcoming) { filteredGames = filteredGames.filter((game: any) => game.gameState !== "FUT" && game.gameState !== "PRE" ); } if (filteredGames.length === 0) { formattedScores.push("No games matching the specified filters."); return; } // Group games by state const liveGamesArray = filteredGames.filter((game: any) => game.gameState === "LIVE"); const completedGamesArray = filteredGames.filter((game: any) => game.gameState === "FINAL" || game.gameState === "OFF" ); const upcomingGamesArray = filteredGames.filter((game: any) => game.gameState === "FUT" || game.gameState === "PRE" ); // Update counters liveGames += liveGamesArray.length; completedGames += completedGamesArray.length; upcomingGames += upcomingGamesArray.length; totalGames += filteredGames.length; // Display live games first if (liveGamesArray.length > 0) { formattedScores.push("### 🔴 Live Games\n"); formattedScores.push("| Away | Score | Home | Score | Status | Time |"); formattedScores.push("|------|-------|------|-------|--------|------|"); liveGamesArray.forEach((game: any) => { const awayTeam = game.awayTeam ? safeString(game.awayTeam.abbrev) : "N/A"; const homeTeam = game.homeTeam ? safeString(game.homeTeam.abbrev) : "N/A"; const awayScore = game.awayTeam ? game.awayTeam.score : 0; const homeScore = game.homeTeam ? game.homeTeam.score : 0; // Format period info let periodInfo = "1st"; if (game.periodDescriptor) { periodInfo = `${game.periodDescriptor.number}${game.periodDescriptor.periodType}`; } // Format clock let clockInfo = "N/A"; if (game.clock && game.clock.timeRemaining) { clockInfo = game.clock.timeRemaining; } formattedScores.push(`| ${awayTeam} | ${awayScore} | ${homeTeam} | ${homeScore} | ${periodInfo} | ${clockInfo} |`); }); formattedScores.push(""); } // Display completed games if (completedGamesArray.length > 0 && includeCompleted) { formattedScores.push("### ✅ Final Scores\n"); formattedScores.push("| Away | Score | Home | Score | Status |"); formattedScores.push("|------|-------|------|-------|--------|"); completedGamesArray.forEach((game: any) => { const awayTeam = game.awayTeam ? safeString(game.awayTeam.abbrev) : "N/A"; const homeTeam = game.homeTeam ? safeString(game.homeTeam.abbrev) : "N/A"; const awayScore = game.awayTeam ? game.awayTeam.score : 0; const homeScore = game.homeTeam ? game.homeTeam.score : 0; // Format status let status = "Final"; if (game.periodDescriptor && game.periodDescriptor.periodType === "OT") { status = "Final (OT)"; } else if (game.periodDescriptor && game.periodDescriptor.periodType === "SO") { status = "Final (SO)"; } formattedScores.push(`| ${awayTeam} | ${awayScore} | ${homeTeam} | ${homeScore} | ${status} |`); }); formattedScores.push(""); } // Display upcoming games if (upcomingGamesArray.length > 0 && includeUpcoming) { formattedScores.push("### 🕒 Upcoming Games\n"); formattedScores.push("| Away | Home | Time (ET) | Venue |"); formattedScores.push("|------|------|-----------|-------|"); upcomingGamesArray.forEach((game: any) => { const awayTeam = game.awayTeam ? safeString(game.awayTeam.abbrev) : "N/A"; const homeTeam = game.homeTeam ? safeString(game.homeTeam.abbrev) : "N/A"; // Format time let gameTime = "TBD"; if (game.startTimeUTC) { try { const date = new Date(game.startTimeUTC); gameTime = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } catch (e) { gameTime = "TBD"; } } // Format venue const venue = game.venue ? safeString(game.venue) : "N/A"; formattedScores.push(`| ${awayTeam} | ${homeTeam} | ${gameTime} | ${venue} |`); }); formattedScores.push(""); } } }); // Add summary formattedScores.push("## Summary"); formattedScores.push(`Total Games Today: ${totalGames}`); if (liveGames > 0) formattedScores.push(`🔴 Live Games: ${liveGames}`); if (completedGames > 0 && includeCompleted) formattedScores.push(`✅ Completed Games: ${completedGames}`); if (upcomingGames > 0 && includeUpcoming) formattedScores.push(`🕒 Upcoming Games: ${upcomingGames}`); // If no matching date was found in the game week if (totalGames === 0) { formattedScores.push("No games scheduled for today."); } } return { content: [ { type: "text", text: formattedScores.join("\n"), }, ], }; } ); server.tool( "get-scores-by-date", "Get NHL scores for a specific date", { date: z.string().describe("Date in YYYY-MM-DD format"), includeCompleted: z.boolean().optional().default(true).describe("Include completed games"), includeUpcoming: z.boolean().optional().default(true).describe("Include upcoming games"), }, async ({ date, includeCompleted = true, includeUpcoming = true }: { date: string; includeCompleted?: boolean; includeUpcoming?: boolean }) => { console.error(`Getting NHL scores for date: ${date} (includeCompleted: ${includeCompleted}, includeUpcoming: ${includeUpcoming})`); // Validate date format (YYYY-MM-DD) const dateRegex = /^\d{4}-\d{2}-\d{2}$/; if (!dateRegex.test(date)) { return { content: [ { type: "text", text: `Invalid date format. Please provide the date in YYYY-MM-DD format.`, }, ], }; } // Fetch schedule data from NHL API const scheduleUrl = `${NHL_WEB_API_BASE}/schedule/${date}`; let scheduleData = null; try { console.error(`Fetching scores from: ${scheduleUrl}`); scheduleData = await makeNHLRequest<any>(scheduleUrl); if (!scheduleData) { return { content: [ { type: "text", text: `Failed to retrieve NHL scores for date: ${date}. The NHL API might be unavailable.`, }, ], }; } } catch (error) { console.error(`Error fetching scores: ${error}`); return { content: [ { type: "text", text: `Error retrieving NHL scores for date: ${date}: ${error}`, }, ], }; } // Safe string extraction function const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) return defaultValue; if (typeof value === 'string') return value; if (typeof value === 'number' || typeof value === 'boolean') return String(value); if (typeof value === 'object' && value.default) return value.default; return defaultValue; }; // Format the scores data const formattedScores = []; // Add header formattedScores.push(`# NHL Scores - ${date}\n`); // Check if we have game week data if (!scheduleData.gameWeek || !Array.isArray(scheduleData.gameWeek) || scheduleData.gameWeek.length === 0) { formattedScores.push("No games scheduled for this date."); } else { // Process each day in the game week let totalGames = 0; let liveGames = 0; let completedGames = 0; let upcomingGames = 0; let foundMatchingDay = false; scheduleData.gameWeek.forEach((day: any) => { if (day.date === date) { foundMatchingDay = true; formattedScores.push(`## ${day.date} (${day.dayAbbrev}) - ${day.numberOfGames} games\n`); if (!day.games || day.games.length === 0) { formattedScores.push("No games scheduled for this date."); return; } // Filter games based on parameters let filteredGames = day.games; if (!includeCompleted) { filteredGames = filteredGames.filter((game: any) => game.gameState !== "FINAL" && game.gameState !== "OFF" ); } if (!includeUpcoming) { filteredGames = filteredGames.filter((game: any) => game.gameState !== "FUT" && game.gameState !== "PRE" ); } if (filteredGames.length === 0) { formattedScores.push("No games matching the specified filters."); return; } // Group games by state const liveGamesArray = filteredGames.filter((game: any) => game.gameState === "LIVE"); const completedGamesArray = filteredGames.filter((game: any) => game.gameState === "FINAL" || game.gameState === "OFF" ); const upcomingGamesArray = filteredGames.filter((game: any) => game.gameState === "FUT" || game.gameState === "PRE" ); // Update counters liveGames += liveGamesArray.length; completedGames += completedGamesArray.length; upcomingGames += upcomingGamesArray.length; totalGames += filteredGames.length; // Display live games first if (liveGamesArray.length > 0) { formattedScores.push("### 🔴 Live Games\n"); formattedScores.push("| Away | Score | Home | Score | Status | Time |"); formattedScores.push("|------|-------|------|-------|--------|------|"); liveGamesArray.forEach((game: any) => { const awayTeam = game.awayTeam ? safeString(game.awayTeam.abbrev) : "N/A"; const homeTeam = game.homeTeam ? safeString(game.homeTeam.abbrev) : "N/A"; const awayScore = game.awayTeam ? game.awayTeam.score : 0; const homeScore = game.homeTeam ? game.homeTeam.score : 0; // Format period info let periodInfo = "1st"; if (game.periodDescriptor) { periodInfo = `${game.periodDescriptor.number}${game.periodDescriptor.periodType}`; } // Format clock let clockInfo = "N/A"; if (game.clock && game.clock.timeRemaining) { clockInfo = game.clock.timeRemaining; } formattedScores.push(`| ${awayTeam} | ${awayScore} | ${homeTeam} | ${homeScore} | ${periodInfo} | ${clockInfo} |`); }); formattedScores.push(""); } // Display completed games if (completedGamesArray.length > 0 && includeCompleted) { formattedScores.push("### ✅ Final Scores\n"); formattedScores.push("| Away | Score | Home | Score | Status |"); formattedScores.push("|------|-------|------|-------|--------|"); completedGamesArray.forEach((game: any) => { const awayTeam = game.awayTeam ? safeString(game.awayTeam.abbrev) : "N/A"; const homeTeam = game.homeTeam ? safeString(game.homeTeam.abbrev) : "N/A"; const awayScore = game.awayTeam ? game.awayTeam.score : 0; const homeScore = game.homeTeam ? game.homeTeam.score : 0; // Format status let status = "Final"; if (game.periodDescriptor && game.periodDescriptor.periodType === "OT") { status = "Final (OT)"; } else if (game.periodDescriptor && game.periodDescriptor.periodType === "SO") { status = "Final (SO)"; } formattedScores.push(`| ${awayTeam} | ${awayScore} | ${homeTeam} | ${homeScore} | ${status} |`); }); formattedScores.push(""); } // Display upcoming games if (upcomingGamesArray.length > 0 && includeUpcoming) { formattedScores.push("### 🕒 Upcoming Games\n"); formattedScores.push("| Away | Home | Time (ET) | Venue |"); formattedScores.push("|------|------|-----------|-------|"); upcomingGamesArray.forEach((game: any) => { const awayTeam = game.awayTeam ? safeString(game.awayTeam.abbrev) : "N/A"; const homeTeam = game.homeTeam ? safeString(game.homeTeam.abbrev) : "N/A"; // Format time let gameTime = "TBD"; if (game.startTimeUTC) { try { const date = new Date(game.startTimeUTC); gameTime = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } catch (e) { gameTime = "TBD"; } } // Format venue const venue = game.venue ? safeString(game.venue) : "N/A"; formattedScores.push(`| ${awayTeam} | ${homeTeam} | ${gameTime} | ${venue} |`); }); formattedScores.push(""); } // Add team leaders if available const gamesWithLeaders = filteredGames.filter((game: any) => game.teamLeaders && game.teamLeaders.length > 0); if (gamesWithLeaders.length > 0) { formattedScores.push("### 🏆 Team Leaders\n"); gamesWithLeaders.forEach((game: any) => { const awayTeam = game.awayTeam ? safeString(game.awayTeam.abbrev) : "N/A"; const homeTeam = game.homeTeam ? safeString(game.homeTeam.abbrev) : "N/A"; formattedScores.push(`#### ${awayTeam} @ ${homeTeam}\n`); if (game.teamLeaders && game.teamLeaders.length > 0) { formattedScores.push("| Team | Player | Position | Category | Value |"); formattedScores.push("|------|--------|----------|----------|-------|"); game.teamLeaders.forEach((leader: any) => { const team = leader.teamAbbrev || "N/A"; const playerName = `${safeString(leader.firstName)} ${safeString(leader.lastName)}`; const position = leader.position || "N/A"; const category = leader.category || "N/A"; const value = leader.value || 0; formattedScores.push(`| ${team} | ${playerName} | ${position} | ${category} | ${value} |`); }); formattedScores.push(""); } }); } } }); // Add summary if (foundMatchingDay) { formattedScores.push("## Summary"); formattedScores.push(`Total Games: ${totalGames}`); if (liveGames > 0) formattedScores.push(`🔴 Live Games: ${liveGames}`); if (completedGames > 0 && includeCompleted) formattedScores.push(`✅ Completed Games: ${completedGames}`); if (upcomingGames > 0 && includeUpcoming) formattedScores.push(`🕒 Upcoming Games: ${upcomingGames}`); } else { formattedScores.push("No games scheduled for this date."); } } return { content: [ { type: "text", text: formattedScores.join("\n"), }, ], }; } ); server.tool( "get-game-play-by-play", "Get play-by-play data for a specific NHL game", { gameId: z.string().describe("Game ID in the format YYYYYYYY (e.g., 2023020204)"), maxEvents: z.number().optional().default(50).describe("Maximum number of events to return (default: 50, use 0 for all)"), filterByType: z.string().optional().describe("Filter events by type (e.g., 'goal', 'penalty', 'shot-on-goal', 'hit')"), }, async ({ gameId, maxEvents = 50, filterByType }: { gameId: string; maxEvents?: number; filterByType?: string }) => { console.error(`Getting play-by-play data for game ${gameId} (maxEvents: ${maxEvents}, filterByType: ${filterByType || 'none'})`); // Fetch play-by-play data from NHL API const playByPlayUrl = `${NHL_WEB_API_BASE}/gamecenter/${gameId}/play-by-play`; let playByPlayData = null; try { console.error(`Fetching play-by-play data from: ${playByPlayUrl}`); playByPlayData = await makeNHLRequest<any>(playByPlayUrl); if (!playByPlayData) { return { content: [ { type: "text", text: `Failed to retrieve play-by-play data. The NHL API might be unavailable or the game ID might be invalid.`, }, ], }; } } catch (error) { console.error(`Error fetching play-by-play data: ${error}`); return { content: [ { type: "text", text: `Error retrieving play-by-play data: ${error}`, }, ], }; } // Helper function for safe string extraction const safeString = (value: any, defaultValue: string = "N/A"): string => { if (value === null || value === undefined) { return defaultValue; } if (typeof value === "object" && value.default) { return value.default; } return String(value); }; // Create a map of player IDs to names const playerMap = new Map<number, string>(); if (playByPlayData.rosterSpots && Array.isArray(playByPlayData.rosterSpots)) { playByPlayData.rosterSpots.forEach((player: any) => { if (player.playerId && player.firstName && player.lastName) { const firstName = safeString(player.firstName); const lastName = safeString(player.lastName); playerMap.set(player.playerId, `${firstName} ${lastName}`); } }); } // Helper function to get player name from ID const getPlayerName = (playerId: number): string => { return playerMap.get(playerId) || `Player #${playerId}`; }; // Format the play-by-play data const formattedPlayByPlay: string[] = []; // Add game information header const homeTeam = playByPlayData.homeTeam ? `${safeString(playByPlayData.homeTeam.placeName)} ${safeString(playByPlayData.homeTeam.commonName)}` : "Home Team"; const awayTeam = playByPlayData.awayTeam ? `${safeString(playByPlayData.awayTeam.placeName)} ${safeString(playByPlayData.awayTeam.commonName)}` : "Away Team"; const homeScore = playByPlayData.homeTeam?.score || 0; const awayScore = playByPlayData.awayTeam?.score || 0; const venue = safeString(playByPlayData.venue); const gameDate = safeString(playByPlayData.gameDate); formattedPlayByPlay.push(`# Play-by-Play: ${awayTeam} (${awayScore}) @ ${homeTeam} (${homeScore})`); formattedPlayByPlay.push(`**Game ID:** ${gameId} | **Date:** ${gameDate} | **Venue:** ${venue}`); formattedPlayByPlay.push(""); // Process plays if (playByPlayData.plays && Array.isArray(playByPlayData.plays)) { let plays = playByPlayData.plays; // Filter by type if specified if (filterByType) { const filterLower = filterByType.toLowerCase(); plays = plays.filter((play: any) => { const typeDescKey = play.typeDescKey?.toLowerCase() || ''; return typeDescKey.includes(filterLower); }); } // Limit the number of events if specified if (maxEvents > 0 && plays.length > maxEvents) { formattedPlayByPlay.push(`*Showing ${maxEvents} of ${plays.length} total events. Use maxEvents=0 to see all events.*`); plays = plays.slice(0, maxEvents); } // Add table header formattedPlayByPlay.push("| Period | Time | Event | Details |"); formattedPlayByPlay.push("|--------|------|-------|---------|"); // Add plays to table plays.forEach((play: any) => { const period = play.periodDescriptor ? `${play.periodDescriptor.number} ${play.periodDescriptor.periodType}` : "N/A"; const timeInPeriod = play.timeInPeriod || "00:00"; const eventType = safeString(play.typeDescKey).replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); let details = ""; if (play.details) { if (play.typeDescKey === "goal") { const scorer = play.details.scoringPlayerId ? `**Goal:** ${getPlayerName(play.details.scoringPlayerId)}` : "**Goal**"; let assists = ""; if (play.details.assist1PlayerId) { assists += `, Assist: ${getPlayerName(play.details.assist1PlayerId)}`; if (play.details.assist2PlayerId) { assists += `, ${getPlayerName(play.details.assist2PlayerId)}`; } } const shotType = play.details.shotType ? ` (${play.details.shotType})` : ""; details = `${scorer}${assists}${shotType}`; // Add score if (play.details.awayScore !== undefined && play.details.homeScore !== undefined) { details += ` | Score: ${awayTeam} ${play.details.awayScore}, ${homeTeam} ${play.details.homeScore}`; } // Add highlight clip if available if (play.details.highlightClipSharingUrl) { details += ` | [Watch](${play.details.highlightClipSharingUrl})`; } } else if (play.typeDescKey === "penalty") { const penaltyType = safeString(play.details.descKey).replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); const player = play.details.committedByPlayerId ? getPlayerName(play.details.committedByPlayerId) : "Unknown player"; const duration = play.details.duration || 2; details = `**${penaltyType}** by ${player} (${duration} min)`; if (play.details.drawnByPlayerId) { details += ` | Drawn by: ${getPlayerName(play.details.drawnByPlayerId)}`; } } else if (play.typeDescKey === "shot-on-goal") { const player = play.details.shootingPlayerId ? getPlayerName(play.details.shootingPlayerId) : "Unknown player"; const shotType = play.details.shotType ? ` (${play.details.shotType})` : ""; details = `Shot by ${player}${shotType}`; if (play.details.goalieInNetId) { details += ` | Saved by: ${getPlayerName(play.details.goalieInNetId)}`; } } else if (play.typeDescKey === "missed-shot") { const player = play.details.shootingPlayerId ? getPlayerName(play.details.shootingPlayerId) : "Unknown player"; const shotType = play.details.shotType ? ` (${play.details.shotType})` : ""; const reason = play.details.reason ? ` - ${play.details.reason.replace(/-/g, ' ')}` : ""; details = `Missed shot by ${player}${shotType}${reason}`; } else if (play.typeDescKey === "blocked-shot") { const shooter = play.details.shootingPlayerId ? getPlayerName(play.details.shootingPlayerId) : "Unknown player"; const blocker = play.details.blockingPlayerId ? getPlayerName(play.details.blockingPlayerId) : "Unknown player"; details = `Shot by ${shooter} blocked by ${blocker}`; } else if (play.typeDescKey === "hit") { const hitter = play.details.hittingPlayerId ? getPlayerName(play.details.hittingPlayerId) : "Unknown player"; const hittee = play.details.hitteePlayerId ? getPlayerName(play.details.hitteePlayerId) : "Unknown player"; details = `${hitter} hit ${hittee}`; } else if (play.typeDescKey === "faceoff") { const winner = play.details.winningPlayerId ? getPlayerName(play.details.winningPlayerId) : "Unknown player"; const loser = play.details.losingPlayerId ? getPlayerName(play.details.losingPlayerId) : "Unknown player"; details = `${winner} won faceoff against ${loser}`; } else if (play.typeDescKey === "takeaway") { const player = play.details.playerId ? getPlayerName(play.details.playerId) : "Unknown player"; details = `Takeaway by ${player}`; } else if (play.typeDescKey === "giveaway") { const player = play.details.playerId ? getPlayerName(play.details.playerId) : "Unknown player"; details = `Giveaway by ${player}`; } else if (play.typeDescKey === "period-start") { details = `Period ${period} started`; } else if (play.typeDescKey === "period-end") { details = `Period ${period} ended`; } else if (play.typeDescKey === "game-end") { details = `Game ended`; } else if (play.typeDescKey === "stoppage") { details = `Stoppage: ${safeString(play.details.reason).replace(/-/g, ' ')}`; } else { // Generic details for other event types details = Object.entries(play.details) .filter(([key]) => !["eventOwnerTeamId", "zoneCode", "xCoord", "yCoord"].includes(key)) .map(([key, value]) => { if (key.includes("PlayerId") && typeof value === "number") { return `${key}: ${getPlayerName(value as number)}`; } return `${key}: ${value}`; }) .join(", "); } } formattedPlayByPlay.push(`| ${period} | ${timeInPeriod} | ${eventType} | ${details} |`); }); } else { formattedPlayByPlay.push("No play-by-play data available for this game."); } // Add summary formattedPlayByPlay.push(""); formattedPlayByPlay.push("## Game Summary"); formattedPlayByPlay.push(`**Final Score:** ${awayTeam} ${awayScore}, ${homeTeam} ${homeScore}`); if (playByPlayData.gameState) { formattedPlayByPlay.push(`**Game State:** ${playByPlayData.gameState}`); } // Add period summary if (playByPlayData.summary) { formattedPlayByPlay.push(""); formattedPlayByPlay.push("## Period Summary"); formattedPlayByPlay.push("| Period | Away Shots | Home Shots | Away Goals | Home Goals |"); formattedPlayByPlay.push("|--------|------------|------------|-----------|-----------|"); // Add period stats if available if (playByPlayData.summary.byPeriod && Array.isArray(playByPlayData.summary.byPeriod)) { playByPlayData.summary.byPeriod.forEach((period: any) => { const periodNum = period.periodDescriptor ? `${period.periodDescriptor.number} ${period.periodDescriptor.periodType}` : "N/A"; const awaySog = period.away?.shotsOnGoal || 0; const homeSog = period.home?.shotsOnGoal || 0; const awayGoals = period.away?.goals || 0; const homeGoals = period.home?.goals || 0; formattedPlayByPlay.push(`| ${periodNum} | ${awaySog} | ${homeSog} | ${awayGoals} | ${homeGoals} |`); }); } // Add totals if (playByPlayData.summary.totals) { const awaySog = playByPlayData.summary.totals.away?.shotsOnGoal || 0; const homeSog = playByPlayData.summary.totals.home?.shotsOnGoal || 0; const awayGoals = playByPlayData.summary.totals.away?.goals || 0; const homeGoals = playByPlayData.summary.totals.home?.goals || 0; formattedPlayByPlay.push(`| **Total** | **${awaySog}** | **${homeSog}** | **${awayGoals}** | **${homeGoals}** |`); } } return { content: [ { type: "text", text: formattedPlayByPlay.join("\n"), }, ], }; } ); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("NHL API MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dylangroos/nhl-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server