fpl-mcp-server 0.1.6__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
src/tools/teams.py CHANGED
@@ -4,7 +4,7 @@ from pydantic import BaseModel, ConfigDict, Field
4
4
 
5
5
  from ..client import FPLClient
6
6
  from ..constants import CHARACTER_LIMIT
7
- from ..formatting import format_difficulty_indicator, format_team_details
7
+ from ..formatting import format_difficulty_indicator
8
8
  from ..state import store
9
9
  from ..utils import (
10
10
  ResponseFormat,
@@ -15,34 +15,6 @@ from ..utils import (
15
15
  from . import mcp
16
16
 
17
17
 
18
- class GetTeamInfoInput(BaseModel):
19
- """Input model for getting team information."""
20
-
21
- model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True)
22
-
23
- team_name: str = Field(
24
- ...,
25
- description="Team name or abbreviation (e.g., 'Arsenal', 'MCI', 'Liverpool')",
26
- min_length=2,
27
- max_length=50,
28
- )
29
- response_format: ResponseFormat = Field(
30
- default=ResponseFormat.MARKDOWN,
31
- description="Output format: 'markdown' for human-readable or 'json' for machine-readable",
32
- )
33
-
34
-
35
- class ListAllTeamsInput(BaseModel):
36
- """Input model for listing all teams."""
37
-
38
- model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True)
39
-
40
- response_format: ResponseFormat = Field(
41
- default=ResponseFormat.MARKDOWN,
42
- description="Output format: 'markdown' for human-readable or 'json' for machine-readable",
43
- )
44
-
45
-
46
18
  class AnalyzeTeamFixturesInput(BaseModel):
47
19
  """Input model for analyzing team fixtures."""
48
20
 
@@ -71,150 +43,6 @@ async def _create_client():
71
43
  return client
72
44
 
73
45
 
74
- @mcp.tool(
75
- name="fpl_get_team_info",
76
- annotations={
77
- "title": "Get FPL Team Information",
78
- "readOnlyHint": True,
79
- "destructiveHint": False,
80
- "idempotentHint": True,
81
- "openWorldHint": True,
82
- },
83
- )
84
- async def fpl_get_team_info(params: GetTeamInfoInput) -> str:
85
- """
86
- Get detailed information about a specific Premier League team.
87
-
88
- Returns team strength ratings for home/away attack/defence, useful for assessing
89
- which teams have strong defensive or attacking potential.
90
-
91
- Args:
92
- params (GetTeamInfoInput): Validated input parameters containing:
93
- - team_name (str): Team name or abbreviation (e.g., 'Arsenal', 'MCI')
94
- - response_format (ResponseFormat): 'markdown' or 'json' (default: markdown)
95
-
96
- Returns:
97
- str: Detailed team information with strength ratings
98
-
99
- Examples:
100
- - Get Arsenal info: team_name="Arsenal"
101
- - Use abbreviation: team_name="LIV"
102
- - Get JSON format: team_name="Man City", response_format="json"
103
-
104
- Error Handling:
105
- - Returns error if no team found
106
- - Returns error if multiple teams match (asks user to be more specific)
107
- - Returns formatted error message if data unavailable
108
- """
109
- try:
110
- await _create_client()
111
- if not store.bootstrap_data:
112
- return "Error: Team data not available. Please try again later."
113
-
114
- matching_teams = [
115
- t
116
- for t in store.bootstrap_data.teams
117
- if params.team_name.lower() in t.name.lower()
118
- or params.team_name.lower() in t.short_name.lower()
119
- ]
120
-
121
- if not matching_teams:
122
- return f"No team found matching '{params.team_name}'. Try using the full team name or abbreviation."
123
-
124
- if len(matching_teams) > 1:
125
- team_list = ", ".join([f"{t.name} ({t.short_name})" for t in matching_teams])
126
- return f"Multiple teams found: {team_list}. Please be more specific."
127
-
128
- team = matching_teams[0]
129
- team_dict = store.get_team_by_id(team.id)
130
-
131
- if params.response_format == ResponseFormat.JSON:
132
- return format_json_response(team_dict)
133
- else:
134
- # Convert Team object to dict for formatter
135
- team_dict = {
136
- "name": team.name,
137
- "short_name": team.short_name,
138
- "strength": getattr(team, "strength", None),
139
- "strength_overall_home": getattr(team, "strength_overall_home", None),
140
- "strength_overall_away": getattr(team, "strength_overall_away", None),
141
- "strength_attack_home": getattr(team, "strength_attack_home", None),
142
- "strength_attack_away": getattr(team, "strength_attack_away", None),
143
- "strength_defence_home": getattr(team, "strength_defence_home", None),
144
- "strength_defence_away": getattr(team, "strength_defence_away", None),
145
- }
146
-
147
- result = format_team_details(team_dict)
148
- truncated, _ = check_and_truncate(result, CHARACTER_LIMIT)
149
- return truncated
150
-
151
- except Exception as e:
152
- return handle_api_error(e)
153
-
154
-
155
- @mcp.tool(
156
- name="fpl_list_all_teams",
157
- annotations={
158
- "title": "List All FPL Teams",
159
- "readOnlyHint": True,
160
- "destructiveHint": False,
161
- "idempotentHint": True,
162
- "openWorldHint": True,
163
- },
164
- )
165
- async def fpl_list_all_teams(params: ListAllTeamsInput) -> str:
166
- """
167
- List all Premier League teams with their basic information.
168
-
169
- Returns all 20 Premier League teams with their names, abbreviations, and average
170
- strength ratings. Useful for finding exact team names or comparing team strengths.
171
-
172
- Args:
173
- params (ListAllTeamsInput): Validated input parameters containing:
174
- - response_format (ResponseFormat): 'markdown' or 'json' (default: markdown)
175
-
176
- Returns:
177
- str: List of all teams with strength ratings
178
-
179
- Examples:
180
- - List all teams: response_format="markdown"
181
- - Get as JSON: response_format="json"
182
-
183
- Error Handling:
184
- - Returns error if team data unavailable
185
- - Returns formatted error message if API fails
186
- """
187
- try:
188
- await _create_client()
189
- teams = store.get_all_teams()
190
- if not teams:
191
- return "Error: Team data not available. Please try again later."
192
-
193
- teams_sorted = sorted(teams, key=lambda t: t["name"])
194
-
195
- if params.response_format == ResponseFormat.JSON:
196
- return format_json_response({"count": len(teams_sorted), "teams": teams_sorted})
197
- else:
198
- output = ["**Premier League Teams:**\n"]
199
-
200
- for team in teams_sorted:
201
- strength_info = ""
202
- if team.get("strength_overall_home") and team.get("strength_overall_away"):
203
- avg_strength = (
204
- team["strength_overall_home"] + team["strength_overall_away"]
205
- ) / 2
206
- strength_info = f" | Strength: {avg_strength:.0f}"
207
-
208
- output.append(f"{team['name']:20s} ({team['short_name']}){strength_info}")
209
-
210
- result = "\n".join(output)
211
- truncated, _ = check_and_truncate(result, CHARACTER_LIMIT)
212
- return truncated
213
-
214
- except Exception as e:
215
- return handle_api_error(e)
216
-
217
-
218
46
  @mcp.tool(
219
47
  name="fpl_analyze_team_fixtures",
220
48
  annotations={
src/tools/transfers.py CHANGED
@@ -4,31 +4,17 @@ from pydantic import BaseModel, ConfigDict, Field
4
4
 
5
5
  from ..client import FPLClient
6
6
  from ..constants import CHARACTER_LIMIT
7
+ from ..formatting import format_player_price
7
8
  from ..state import store
8
9
  from ..utils import (
9
10
  ResponseFormat,
10
11
  check_and_truncate,
11
12
  format_json_response,
12
- format_player_price,
13
13
  handle_api_error,
14
14
  )
15
15
  from . import mcp
16
16
 
17
17
 
18
- class GetPlayerTransfersByGameweekInput(BaseModel):
19
- """Input model for getting player transfer statistics."""
20
-
21
- model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True)
22
-
23
- player_name: str = Field(
24
- ...,
25
- description="Player name (e.g., 'Haaland', 'Salah')",
26
- min_length=2,
27
- max_length=100,
28
- )
29
- gameweek: int = Field(..., description="Gameweek number (1-38)", ge=1, le=38)
30
-
31
-
32
18
  class GetTopTransferredPlayersInput(BaseModel):
33
19
  """Input model for getting top transferred players."""
34
20
 
@@ -50,6 +36,32 @@ class GetManagerTransfersByGameweekInput(BaseModel):
50
36
  gameweek: int = Field(..., description="Gameweek number (1-38)", ge=1, le=38)
51
37
 
52
38
 
39
+ class AnalyzeTransferInput(BaseModel):
40
+ """Input model for analyzing a potential transfer."""
41
+
42
+ model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True)
43
+
44
+ player_out: str = Field(
45
+ ...,
46
+ description="Name of player to transfer out (e.g., 'Salah')",
47
+ min_length=2,
48
+ max_length=100,
49
+ )
50
+ player_in: str = Field(
51
+ ...,
52
+ description="Name of player to transfer in (e.g., 'Palmer')",
53
+ min_length=2,
54
+ max_length=100,
55
+ )
56
+ my_team_id: int | None = Field(
57
+ default=None, description="Your team ID to check budget/value impact (optional)"
58
+ )
59
+ response_format: ResponseFormat = Field(
60
+ default=ResponseFormat.MARKDOWN,
61
+ description="Output format: 'markdown' or 'json'",
62
+ )
63
+
64
+
53
65
  async def _create_client():
54
66
  """Create an unauthenticated FPL client for public API access and ensure data is loaded."""
55
67
  client = FPLClient(store=store)
@@ -58,103 +70,6 @@ async def _create_client():
58
70
  return client
59
71
 
60
72
 
61
- @mcp.tool(
62
- name="fpl_get_player_transfers_by_gameweek",
63
- annotations={
64
- "title": "Get FPL Player Transfer Statistics",
65
- "readOnlyHint": True,
66
- "destructiveHint": False,
67
- "idempotentHint": True,
68
- "openWorldHint": True,
69
- },
70
- )
71
- async def fpl_get_player_transfers_by_gameweek(
72
- params: GetPlayerTransfersByGameweekInput,
73
- ) -> str:
74
- """
75
- Get transfer statistics for a specific player in a specific gameweek.
76
-
77
- Shows transfers in, transfers out, net transfers, ownership data, and performance.
78
- Useful for understanding how manager sentiment towards a player changed during a
79
- specific gameweek and correlation with performance.
80
-
81
- Args:
82
- params (GetPlayerTransfersByGameweekInput): Validated input parameters containing:
83
- - player_name (str): Player name (e.g., 'Haaland', 'Salah')
84
- - gameweek (int): Gameweek number between 1-38
85
-
86
- Returns:
87
- str: Transfer statistics and performance for the gameweek
88
-
89
- Examples:
90
- - Check Haaland GW20: player_name="Haaland", gameweek=20
91
- - Salah transfers: player_name="Salah", gameweek=15
92
-
93
- Error Handling:
94
- - Returns error if player not found
95
- - Suggests using fpl_find_player if name ambiguous
96
- - Returns error if no data for gameweek
97
- """
98
- try:
99
- client = await _create_client()
100
-
101
- # Find player by name
102
- matches = store.find_players_by_name(params.player_name, fuzzy=True)
103
- if not matches:
104
- return f"No player found matching '{params.player_name}'. Use fpl_search_players to find the correct name."
105
-
106
- if len(matches) > 1 and matches[0][1] < 0.95:
107
- return f"Ambiguous player name. Use fpl_find_player to see all matches for '{params.player_name}'"
108
-
109
- player = matches[0][0]
110
- player_id = player.id
111
-
112
- # Fetch detailed summary from API
113
- summary_data = await client.get_element_summary(player_id)
114
- history = summary_data.get("history", [])
115
-
116
- # Find the specific gameweek in history
117
- gw_data = next((gw for gw in history if gw.get("round") == params.gameweek), None)
118
-
119
- if not gw_data:
120
- return f"No transfer data found for {player.web_name} in gameweek {params.gameweek}. The gameweek may not have started yet or data is unavailable."
121
-
122
- # Enrich with team name
123
- enriched_history = store.enrich_gameweek_history([gw_data])
124
- if enriched_history:
125
- gw_data = enriched_history[0]
126
-
127
- output = [
128
- f"**{player.web_name}** ({player.first_name} {player.second_name})",
129
- f"Team: {player.team_name} | Position: {player.position} | Price: {format_player_price(player.now_cost)}",
130
- "",
131
- f"**Gameweek {params.gameweek} Transfer Statistics:**",
132
- "",
133
- f"├─ Transfers In: {gw_data.get('transfers_in', 0):,}",
134
- f"├─ Transfers Out: {gw_data.get('transfers_out', 0):,}",
135
- f"├─ Net Transfers: {gw_data.get('transfers_balance', gw_data.get('transfers_in', 0) - gw_data.get('transfers_out', 0)):+,}",
136
- f"├─ Ownership at GW: {gw_data.get('selected', 0):,} teams",
137
- "",
138
- f"**Performance in GW{params.gameweek}:**",
139
- f"├─ Points: {gw_data.get('total_points', 0)}",
140
- f"├─ Minutes: {gw_data.get('minutes', 0)}",
141
- f"├─ xGoal: {gw_data.get('expected_goals', '0.00')} | Goals: {gw_data.get('goals_scored', 0)}",
142
- f"├─ xAssist: {gw_data.get('expected_assists', '0.00')} | Assists: {gw_data.get('assists', 0)}",
143
- f"├─ Clean Sheets: {gw_data.get('clean_sheets', 0)} | Bonus: {gw_data.get('bonus', 0)}",
144
- ]
145
-
146
- opponent_name = gw_data.get("opponent_team_short", "Unknown")
147
- home_away = "H" if gw_data.get("was_home") else "A"
148
- output.append(f"├─ Opponent: vs {opponent_name} ({home_away})")
149
-
150
- result = "\n".join(output)
151
- truncated, _ = check_and_truncate(result, CHARACTER_LIMIT)
152
- return truncated
153
-
154
- except Exception as e:
155
- return handle_api_error(e)
156
-
157
-
158
73
  @mcp.tool(
159
74
  name="fpl_get_top_transferred_players",
160
75
  annotations={
@@ -611,3 +526,226 @@ async def fpl_get_manager_chips(params: GetManagerChipsInput) -> str:
611
526
 
612
527
  except Exception as e:
613
528
  return handle_api_error(e)
529
+
530
+
531
+ @mcp.tool(
532
+ name="fpl_analyze_transfer",
533
+ annotations={
534
+ "title": "Analyze FPL Transfer",
535
+ "readOnlyHint": True,
536
+ "destructiveHint": False,
537
+ "idempotentHint": True,
538
+ "openWorldHint": True,
539
+ },
540
+ )
541
+ async def fpl_analyze_transfer(params: AnalyzeTransferInput) -> str:
542
+ """
543
+ Analyze a potential transfer decision between two players.
544
+
545
+ Compares the player being transferred out vs the player being transferred in.
546
+ Analyzes form, upcoming fixtures (next 5), price difference, and overall value.
547
+ Provides a direct recommendation based on the data.
548
+
549
+ Args:
550
+ params (AnalyzeTransferInput): Validated input parameters containing:
551
+ - player_out (str): Name of player to remove
552
+ - player_in (str): Name of player to add
553
+ - my_team_id (int | None): Optional team ID to check budget impact
554
+
555
+ Returns:
556
+ str: Detailed transfer analysis and recommendation
557
+
558
+ Examples:
559
+ - Analyze move: player_out="Salah", player_in="Palmer"
560
+ - Check budget: player_out="Saka", player_in="Foden", my_team_id=123456
561
+
562
+ Error Handling:
563
+ - Returns error if either player not found
564
+ - Returns error if players play different positions (unless specified)
565
+ - Returns formatted error message if API fails
566
+ """
567
+ try:
568
+ client = await _create_client()
569
+ if not store.bootstrap_data:
570
+ return "Error: Player data not available. Please try again later."
571
+
572
+ # Helper to find player
573
+ def find_player(name):
574
+ matches = store.find_players_by_name(name, fuzzy=True)
575
+ if not matches:
576
+ return None, f"Player '{name}' not found."
577
+ if (
578
+ len(matches) > 1
579
+ and matches[0][1] < 0.95
580
+ and not (matches[0][1] - matches[1][1] > 0.2)
581
+ ):
582
+ return (
583
+ None,
584
+ f"Ambiguous name '{name}'. Did you mean: {', '.join(m[0].web_name for m in matches[:3])}?",
585
+ )
586
+ return matches[0][0], None
587
+
588
+ # Find both players
589
+ p_out, err_out = find_player(params.player_out)
590
+ if err_out:
591
+ return f"Error finding player_out: {err_out}"
592
+
593
+ p_in, err_in = find_player(params.player_in)
594
+ if err_in:
595
+ return f"Error finding player_in: {err_in}"
596
+
597
+ # Check positions
598
+ pos_names = {1: "GKP", 2: "DEF", 3: "MID", 4: "FWD"}
599
+ if p_out.element_type != p_in.element_type:
600
+ # element_type 1=GKP, 2=DEF, 3=MID, 4=FWD
601
+ # Get position names
602
+ return (
603
+ f"Invalid transfer: {p_out.web_name} is a {pos_names.get(p_out.element_type)} "
604
+ f"while {p_in.web_name} is a {pos_names.get(p_in.element_type)}. "
605
+ f"Transfers must be between players of the same position."
606
+ )
607
+
608
+ # Get fixtures for next 5 gameweeks
609
+ current_gw_data = store.get_current_gameweek()
610
+ start_gw = (current_gw_data.id + 1) if current_gw_data else 1
611
+ # Handle case where season is over
612
+ if start_gw > 38:
613
+ start_gw = 38
614
+
615
+ # Fetch detailed summaries for both
616
+ summary_out = await client.get_element_summary(p_out.id)
617
+ summary_in = await client.get_element_summary(p_in.id)
618
+
619
+ # Process Fixtures
620
+ fixtures_out = store.enrich_fixtures(summary_out.get("fixtures", []))
621
+ fixtures_in = store.enrich_fixtures(summary_in.get("fixtures", []))
622
+
623
+ # Filter next 5 fixtures
624
+ next_5_out = [f for f in fixtures_out if f["event"] and f["event"] >= start_gw][:5]
625
+ next_5_in = [f for f in fixtures_in if f["event"] and f["event"] >= start_gw][:5]
626
+
627
+ # Calculate difficulty score (lower is easier)
628
+ def calc_difficulty(fixtures, is_attacker):
629
+ total = 0
630
+ count = 0
631
+ for f in fixtures:
632
+ diff = (
633
+ f.get("team_h_difficulty") if f.get("is_home") else f.get("team_a_difficulty")
634
+ )
635
+ if diff is None:
636
+ diff = 3 # Default to average if missing
637
+ total += int(diff)
638
+ count += 1
639
+ return total / count if count > 0 else 0
640
+
641
+ # Heuristic: MIDs and FWDs are attackers, DEF and GKP defenders
642
+ is_attacker = p_out.element_type in [3, 4]
643
+ diff_out = calc_difficulty(next_5_out, is_attacker)
644
+ diff_in = calc_difficulty(next_5_in, is_attacker)
645
+
646
+ # Calculate budget impact
647
+ price_diff = p_in.now_cost - p_out.now_cost
648
+
649
+ if params.response_format == ResponseFormat.JSON:
650
+ result = {
651
+ "transfer": {
652
+ "out": p_out.web_name,
653
+ "in": p_in.web_name,
654
+ "position": p_out.position,
655
+ },
656
+ "analysis": {
657
+ "price_change": price_diff / 10,
658
+ "fixture_diff_score": {
659
+ "out": round(diff_out, 2),
660
+ "in": round(diff_in, 2),
661
+ "easier_fixtures": "in" if diff_in < diff_out else "out",
662
+ },
663
+ "form": {"out": float(p_out.form), "in": float(p_in.form)},
664
+ "points_per_game": {
665
+ "out": float(p_out.points_per_game),
666
+ "in": float(p_in.points_per_game),
667
+ },
668
+ },
669
+ "recommendation": "TRANSFER IN"
670
+ if diff_in < diff_out and float(p_in.form) > float(p_out.form)
671
+ else "HOLD",
672
+ }
673
+ return format_json_response(result)
674
+
675
+ # Markdown Output
676
+ output = [
677
+ f"## Transfer Analysis: {p_out.web_name} ➔ {p_in.web_name}",
678
+ f"**Position:** {pos_names.get(p_out.element_type)} | **Budget Impact:** {format_player_price(price_diff)}",
679
+ "",
680
+ "### 📊 Head-to-Head Comparison",
681
+ f"| Metric | ❌ {p_out.web_name} (OUT) | ✅ {p_in.web_name} (IN) | Diff |",
682
+ "| :--- | :--- | :--- | :--- |",
683
+ f"| **Price** | {format_player_price(p_out.now_cost)} | {format_player_price(p_in.now_cost)} | {format_player_price(price_diff)} |",
684
+ f"| **Form** | {p_out.form} | {p_in.form} | {float(p_in.form) - float(p_out.form):.1f} |",
685
+ f"| **PPG** | {p_out.points_per_game} | {p_in.points_per_game} | {float(p_in.points_per_game) - float(p_out.points_per_game):.1f} |",
686
+ f"| **Total Pts** | {getattr(p_out, 'total_points', 0)} | {getattr(p_in, 'total_points', 0)} | {getattr(p_in, 'total_points', 0) - getattr(p_out, 'total_points', 0)} |",
687
+ f"| **Ownership** | {getattr(p_out, 'selected_by_percent', '0.0')}% | {getattr(p_in, 'selected_by_percent', '0.0')}% | {float(getattr(p_in, 'selected_by_percent', '0.0')) - float(getattr(p_out, 'selected_by_percent', '0.0')):+.1f}% |",
688
+ "",
689
+ "### 🗓️ Upcoming Fixtures (Next 5)",
690
+ "Lower difficulty score is better (easier fixtures).",
691
+ "",
692
+ f"**{p_out.web_name}** (Avg Diff: {diff_out:.2f})",
693
+ ]
694
+
695
+ # Helper to format fixture string
696
+ def format_fixture(f):
697
+ opp = f.get("team_a_short") if f.get("is_home") else f.get("team_h_short")
698
+ if not opp:
699
+ opp = "UNK"
700
+ diff = f.get("difficulty", "?")
701
+ loc = "H" if f.get("is_home") else "A"
702
+ return f"{opp} ({loc}) [{diff}]"
703
+
704
+ # Format fixtures
705
+ out_fixtures_str = " | ".join([format_fixture(f) for f in next_5_out])
706
+ output.append(f"└─ {out_fixtures_str}")
707
+
708
+ output.append("")
709
+ output.append(f"**{p_in.web_name}** (Avg Diff: {diff_in:.2f})")
710
+ in_fixtures_str = " | ".join([format_fixture(f) for f in next_5_in])
711
+ output.append(f"└─ {in_fixtures_str}")
712
+
713
+ output.append("")
714
+ output.append("### 💡 Recommendation")
715
+
716
+ # Simple Logic
717
+ better_fixtures = diff_in < diff_out
718
+ better_form = float(p_in.form) > float(p_out.form)
719
+ cheaper = price_diff < 0
720
+
721
+ score = 0
722
+ if better_fixtures:
723
+ score += 2
724
+ if better_form:
725
+ score += 1
726
+ if cheaper:
727
+ score += 0.5
728
+ if float(p_in.points_per_game) > float(p_out.points_per_game):
729
+ score += 1
730
+
731
+ if score >= 3:
732
+ output.append(
733
+ f"✅ **Recommended Transfer** - {p_in.web_name} is a strong upgrade with better fixtures and stats."
734
+ )
735
+ elif score >= 1.5:
736
+ output.append(
737
+ f"⚖️ **Consider Transfer** - {p_in.web_name} has some advantages, but it's close."
738
+ )
739
+ else:
740
+ output.append(
741
+ f"🛑 **Hold Transfer** - {p_out.web_name} looks like the better hold right now."
742
+ )
743
+
744
+ # Add availability check
745
+ if p_in.status != "a":
746
+ output.append(f"\n⚠️ **Warning:** {p_in.web_name} is currently flagged: {p_in.news}")
747
+
748
+ return "\n".join(output)
749
+
750
+ except Exception as e:
751
+ return handle_api_error(e)
@@ -1,35 +0,0 @@
1
- src/cache.py,sha256=SeJAmddaY9507Ac5YRnbBBXGOQw_OwpIefB-kn11lDI,4604
2
- src/client.py,sha256=_Tv7TlXD5d3pvXb7AmMCgy3gbZqjOO9EedMORveRU4s,10493
3
- src/config.py,sha256=hfjW-W0gdH0PxmC6gEg-o9SqraajJ6gNy1SIlIOG-F4,845
4
- src/constants.py,sha256=8XkQH1rslnf6VWbJkVY6MmpgRhhS3wjFJhIoZWr91kg,839
5
- src/exceptions.py,sha256=Q8waMbF8Sr1s6lOoAB8-doX0v6EvqZopwQHGxNQ7m-w,2972
6
- src/formatting.py,sha256=aLiJWM2hJw68gyGJ1Nc1nPAyfoSIqwyjPE8svr-7ufo,10236
7
- src/main.py,sha256=C6wX96rm0-b1jSvU2BrTv47hw2FGktkwcqJ5nEM8t5U,977
8
- src/models.py,sha256=P5rIO-UjVQpLUlDQsDV5hw2Tn3s5Xcj6ye8xJkRizGc,10880
9
- src/rate_limiter.py,sha256=GLk3ZRFFvEZxkZAQd-pZ7UxQdrAAUVch3pxe_aMU-J8,3450
10
- src/state.py,sha256=seyygRhlz-K1GtG80os34tnNJ6UkAFA2rVFgupZG2tY,17531
11
- src/utils.py,sha256=WhcWQIXpc1vIjU8hyrGDJyKJSlcbVoG938k_3UMDlCM,7340
12
- src/validators.py,sha256=aU36TUNYWb26fvZH27Xnryrp8gve9DM2phvy7vEnAi8,6891
13
- src/prompts/__init__.py,sha256=Sj7YgIL46wGrmkJq39rpJilPK3blK6oPI-hE2-lBdxY,535
14
- src/prompts/captain_recommendation.py,sha256=2UK4NQMKL8n1m7gLeebkEDhzndGuJXQBt1FLfS1oo2Y,5850
15
- src/prompts/chips.py,sha256=zzv5bqr8HuUAkvXenonrTXVhwNYGMwH9OPSC-c-1Dtg,5524
16
- src/prompts/league_analysis.py,sha256=23rNhCYkU8hSmd5BesXgNgHLFo_B8qgszmw909MPHkA,8095
17
- src/prompts/player_analysis.py,sha256=SGyd0UYWMF0lgml9idfc853UHgXXBT_qLVLf-8PFePU,5242
18
- src/prompts/squad_analysis.py,sha256=7ixTIrvTITvLIE-9ATH744ci_pObWgzx3p5yUqVHmEk,5204
19
- src/prompts/team_analysis.py,sha256=lZZ2R1xlsclwy4UyiokMg41ziuCKAqxgN_CoT1mOvnY,4104
20
- src/prompts/team_selection.py,sha256=tDOiyQYTp-hyKlKVAdjGxZsr1xPfMgApWREjbMtNpXM,3847
21
- src/prompts/transfers.py,sha256=B99xjzJDTRRdwMluANjKxr5DPWB6eg69nZqJ5uyTosA,5448
22
- src/resources/__init__.py,sha256=i7nlLVSLtiIrLtOnyoMiK3KTFGEnct4LXApB4b6URFM,303
23
- src/resources/bootstrap.py,sha256=ViZsGYtr5YqiTtvM_YTkbCr6R6Z9vUBiVSGGI9wwI3s,6970
24
- src/tools/__init__.py,sha256=JjoMoMHrhFRMarpgtOS9AoS9604c0p-yFc0PXoITe-E,510
25
- src/tools/fixtures.py,sha256=rbt565LV4C_gXfM9tTGUKqMRGl-a_jXcOKZ1tVCXkrA,5634
26
- src/tools/gameweeks.py,sha256=wylGJAXSXhmSy7-PdoXm-w4i4jQIXkSaqM27ctK6w_o,14859
27
- src/tools/leagues.py,sha256=tW6FDjLf7pSWjGgsxCCMAyOpHvSxpBfYXxyaNtHQiLU,30308
28
- src/tools/players.py,sha256=9UX1fZJbiUUDBFBMeImcIh8ysIfc1NQV21_298yX1cU,30568
29
- src/tools/teams.py,sha256=wEbLHKivvGw5YhO0tyvxhUMR9nsYyb4-BQWNBbnzGTw,14183
30
- src/tools/transfers.py,sha256=kU7xy3d6wDZ4T38gNIg6UBJWkfh9-fYhasY_uXR7qGE,24021
31
- fpl_mcp_server-0.1.6.dist-info/METADATA,sha256=Pc1pmqRKBJE1ZyRH5IbL_jChwplQ91-hAaFOAwQzgyg,4788
32
- fpl_mcp_server-0.1.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
33
- fpl_mcp_server-0.1.6.dist-info/entry_points.txt,sha256=b3R5hBUMTLVnCGl07NfK7kyq9NCKtpn5Q8OsY79pMek,49
34
- fpl_mcp_server-0.1.6.dist-info/licenses/LICENSE,sha256=HCDOcdX83voRU2Eip214yj6P_tEyjVjCsCW_sixZFPw,1071
35
- fpl_mcp_server-0.1.6.dist-info/RECORD,,