Coverage for src/mls/adapters/inbound/tools/espn.py: 75%

40 statements  

« prev     ^ index     » next       coverage.py v7.15.3, created at 2026-08-05 10:11 +0000

1"""ESPN-backed MCP tools — teams, scoreboard, roster, news, standings, etc.""" 

2 

3import logging 

4 

5from mcp.server.fastmcp import FastMCP 

6 

7from ....application.service import MLSService 

8from ....ports.inbound import Authorizer 

9from ..formatters import ( 

10 _fmt_match_details, 

11 _fmt_news, 

12 _fmt_roster, 

13 _fmt_scoreboard, 

14 _fmt_standings, 

15 _fmt_team, 

16 _fmt_team_schedule, 

17 _fmt_teams, 

18) 

19from ._base import _READ_ANNOTATIONS, _safe_call_authorized 

20 

21logger = logging.getLogger(__name__) 

22 

23 

24def register_espn_tools(mcp: FastMCP, service: MLSService, authorizer: Authorizer) -> None: 

25 """Register the eight ESPN-backed read-only tools on `mcp`.""" 

26 

27 @mcp.tool(annotations=_READ_ANNOTATIONS) 

28 async def get_teams() -> str: 

29 """Get all active MLS teams. 

30 

31 Returns a numbered list of teams with their ID, full name, abbreviation, 

32 and home city. Use the ID or abbreviation with get_team to retrieve 

33 detailed information about a specific team. 

34 """ 

35 logger.info("tool=get_teams") 

36 return await _safe_call_authorized(authorizer, "get_teams", service.get_teams(), _fmt_teams) 

37 

38 @mcp.tool(annotations=_READ_ANNOTATIONS) 

39 async def get_team(team_id: str) -> str: 

40 """Get details for a specific MLS team. 

41 

42 Returns full team information including display name, abbreviation, and 

43 location. Use the numeric ID returned by get_teams. 

44 

45 Args: 

46 team_id: ESPN numeric team ID (e.g. "18418" for Atlanta United FC). 

47 """ 

48 logger.info("tool=get_team team_id=%r", team_id) 

49 return await _safe_call_authorized(authorizer, "get_team", service.get_team(team_id), _fmt_team) 

50 

51 @mcp.tool(annotations=_READ_ANNOTATIONS) 

52 async def get_scoreboard(date: str | None = None, end_date: str | None = None) -> str: 

53 """Get MLS match scores and status for a date or date range. 

54 

55 With no arguments, returns matches for the current matchweek. With 

56 `date` only, returns matches for that single day. With both `date` 

57 and `end_date`, returns every match in the inclusive range. 

58 

59 Args: 

60 date: Optional start date in YYYYMMDD format (e.g. "20260418"). 

61 end_date: Optional end date in YYYYMMDD format. Requires `date`. 

62 """ 

63 logger.info("tool=get_scoreboard date=%r end_date=%r", date, end_date) 

64 return await _safe_call_authorized( 

65 authorizer, "get_scoreboard", service.get_scoreboard(date, end_date), _fmt_scoreboard 

66 ) 

67 

68 @mcp.tool(annotations=_READ_ANNOTATIONS) 

69 async def get_roster(team_id: str) -> str: 

70 """Get the active roster for an MLS team. 

71 

72 Returns each player's jersey number, name, position, citizenship, 

73 and age. Use the team ID returned by get_teams. 

74 

75 Args: 

76 team_id: ESPN numeric team ID (e.g. "18418" for Atlanta United FC). 

77 """ 

78 logger.info("tool=get_roster team_id=%r", team_id) 

79 return await _safe_call_authorized(authorizer, "get_roster", service.get_roster(team_id), _fmt_roster) 

80 

81 @mcp.tool(annotations=_READ_ANNOTATIONS) 

82 async def get_match_details(match_id: str) -> str: 

83 """Get detailed information for a single MLS match. 

84 

85 Returns the score, venue, attendance, and a chronological list of key 

86 events (goals, substitutions, cards). Use the match ID returned by 

87 get_scoreboard or get_team_schedule. 

88 

89 Args: 

90 match_id: ESPN numeric event ID (e.g. "401853883"). 

91 """ 

92 logger.info("tool=get_match_details match_id=%r", match_id) 

93 return await _safe_call_authorized( 

94 authorizer, "get_match_details", service.get_match_details(match_id), _fmt_match_details 

95 ) 

96 

97 @mcp.tool(annotations=_READ_ANNOTATIONS) 

98 async def get_team_schedule(team_id: str) -> str: 

99 """Get all matches for a single MLS team in the current season. 

100 

101 Returns scheduled, in-progress, and completed matches for the team — 

102 with opponent, date, score (if played), and status. 

103 

104 Args: 

105 team_id: ESPN numeric team ID (e.g. "18418" for Atlanta United FC). 

106 """ 

107 logger.info("tool=get_team_schedule team_id=%r", team_id) 

108 return await _safe_call_authorized( 

109 authorizer, "get_team_schedule", service.get_team_schedule(team_id), _fmt_team_schedule 

110 ) 

111 

112 @mcp.tool(annotations=_READ_ANNOTATIONS) 

113 async def get_news(limit: int = 10) -> str: 

114 """Get recent MLS news articles. 

115 

116 Returns each article's headline, publication date, summary, and link 

117 to the full ESPN story. 

118 

119 Args: 

120 limit: Maximum number of articles to return (default 10). 

121 """ 

122 logger.info("tool=get_news limit=%r", limit) 

123 return await _safe_call_authorized(authorizer, "get_news", service.get_news(limit), _fmt_news) 

124 

125 @mcp.tool(annotations=_READ_ANNOTATIONS) 

126 async def get_standings() -> str: 

127 """Get the current MLS standings, grouped by conference. 

128 

129 MLS is split into Eastern and Western Conferences (15 teams each in 

130 the current alignment). The response renders each conference as a 

131 separate numbered table ordered by points descending, with win/loss/tie 

132 record, goals for, goals against, and goal differential. 

133 """ 

134 logger.info("tool=get_standings") 

135 return await _safe_call_authorized(authorizer, "get_standings", service.get_standings(), _fmt_standings)