fpl-mcp-server 0.1.6__py3-none-any.whl → 0.1.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fpl-mcp-server
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Fantasy Premier League MCP Server
5
5
  Project-URL: Homepage, https://github.com/nguyenanhducs/fpl-mcp
6
6
  Project-URL: Repository, https://github.com/nguyenanhducs/fpl-mcp
@@ -41,7 +41,7 @@ A comprehensive **Model Context Protocol (MCP)** server for Fantasy Premier Leag
41
41
 
42
42
  This MCP server provides comprehensive FPL analysis capabilities through:
43
43
 
44
- - **22 Interactive Tools** - Search players, analyze fixtures, compare managers, track transfers, and more
44
+ - **17 Interactive Tools** - Search players, analyze fixtures, compare managers, track transfers, and more
45
45
  - **4 Data Resources** - access to players, teams, and gameweeks bootstrap data
46
46
  - **8 Strategy Prompts** - Structured templates for squad analysis, transfer planning, chip strategy, and captain selection
47
47
  - **Smart Caching** - 4-hour cache for bootstrap data to minimize API calls while keeping data fresh
@@ -11,25 +11,25 @@ src/state.py,sha256=seyygRhlz-K1GtG80os34tnNJ6UkAFA2rVFgupZG2tY,17531
11
11
  src/utils.py,sha256=WhcWQIXpc1vIjU8hyrGDJyKJSlcbVoG938k_3UMDlCM,7340
12
12
  src/validators.py,sha256=aU36TUNYWb26fvZH27Xnryrp8gve9DM2phvy7vEnAi8,6891
13
13
  src/prompts/__init__.py,sha256=Sj7YgIL46wGrmkJq39rpJilPK3blK6oPI-hE2-lBdxY,535
14
- src/prompts/captain_recommendation.py,sha256=2UK4NQMKL8n1m7gLeebkEDhzndGuJXQBt1FLfS1oo2Y,5850
14
+ src/prompts/captain_recommendation.py,sha256=1FI69uS9wNkOZZNnenFBW_JXg9HKU4bEUmixTn-6GJ0,5706
15
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
16
+ src/prompts/league_analysis.py,sha256=bQN-tVC5FmrZEKTIfwM0eLaNc8mia42Qr34o4kaSJ1g,8297
17
+ src/prompts/player_analysis.py,sha256=7BgF_h0us_vxPC5JrqKPsMs-395xrUvfpW0VJ4Bgon8,5234
18
+ src/prompts/squad_analysis.py,sha256=H3COvcHt7uqPyWMm_2SEaN2lgTgwA20lKuotVOdMe3I,5209
19
+ src/prompts/team_analysis.py,sha256=7ypoaTUvrQQeKsysrhdwbzMzjtI2KldB1ztfSGCZArE,4222
20
20
  src/prompts/team_selection.py,sha256=tDOiyQYTp-hyKlKVAdjGxZsr1xPfMgApWREjbMtNpXM,3847
21
- src/prompts/transfers.py,sha256=B99xjzJDTRRdwMluANjKxr5DPWB6eg69nZqJ5uyTosA,5448
21
+ src/prompts/transfers.py,sha256=Gsfey4XmjyYYJcRFfoDl0oNZnAOGsRCt_Ro0ePv43o8,5543
22
22
  src/resources/__init__.py,sha256=i7nlLVSLtiIrLtOnyoMiK3KTFGEnct4LXApB4b6URFM,303
23
23
  src/resources/bootstrap.py,sha256=ViZsGYtr5YqiTtvM_YTkbCr6R6Z9vUBiVSGGI9wwI3s,6970
24
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,,
25
+ src/tools/fixtures.py,sha256=B5K_jtp4MDqI_Wd3_6blEkDn2Qki_mym2WmDSrhzccs,13155
26
+ src/tools/gameweeks.py,sha256=7skxUC6HoCj9hFC0YbjsMceVIhvvjFOoSdApe9JDfP0,8126
27
+ src/tools/leagues.py,sha256=uRUs2gC4Czj-S8qucomI5x4HQ-I7GxRZI4b3E6KObCM,38024
28
+ src/tools/players.py,sha256=_H2LP9s_yE9mD7zgcgVK__cwc4wdcHo9iUe3R6Llyn0,29691
29
+ src/tools/teams.py,sha256=DknehKi6HIu2TSlKBqg91VWNONxLP4NIy99cH5TrWqo,7924
30
+ src/tools/transfers.py,sha256=zpg0ueCRC6MhKiafhU4-gI0SzEzFYGvlZ2EnXqGoJkU,29466
31
+ fpl_mcp_server-0.1.7.dist-info/METADATA,sha256=m53_QwzJv3nkfvfsUgzE_urTaFhsX8jq4egiXr5bR8w,4788
32
+ fpl_mcp_server-0.1.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
33
+ fpl_mcp_server-0.1.7.dist-info/entry_points.txt,sha256=b3R5hBUMTLVnCGl07NfK7kyq9NCKtpn5Q8OsY79pMek,49
34
+ fpl_mcp_server-0.1.7.dist-info/licenses/LICENSE,sha256=HCDOcdX83voRU2Eip214yj6P_tEyjVjCsCW_sixZFPw,1071
35
+ fpl_mcp_server-0.1.7.dist-info/RECORD,,
@@ -9,7 +9,9 @@ from ..tools import mcp
9
9
 
10
10
 
11
11
  @mcp.prompt()
12
- def recommend_captain(team_id: int, gameweek: int | None = None, response_format: str = "markdown") -> str:
12
+ def recommend_captain(
13
+ team_id: int, gameweek: int | None = None, response_format: str = "markdown"
14
+ ) -> str:
13
15
  """
14
16
  Recommend optimal captain choices using xGI-based metrics and fixture analysis.
15
17
 
@@ -33,19 +35,16 @@ Act as an FPL Expert Analyst with 10+ years of experience. We do not play it saf
33
35
 
34
36
  ## 🚦 **Workflow & Efficiency**
35
37
 
36
- **DO NOT** analyze all 15 players in depth. That is inefficient.
37
- 1. **Get Squad**: Fetch manager's team.
38
- 2. **Shortlist**: Identify **3-5 Candidates** based on:
39
- * **Price**: > £7.0m (Premiums usually haul)
40
- * **Form**: > 4.0 PPG
41
- * **Context**: Key talismen (e.g., Salah, Haaland, Palmer, Saka) even if form is dip.
42
- 3. **Deep Dive**: Only fetch detailed stats (`fpl_compare_players`) for these 3-5 candidates.
38
+ 1. **Tool**: `fpl_get_captain_recommendations(team_id={team_id}, gameweek={gameweek})`
39
+ *This tool automatically runs the Pro-Level Scoring Model defined below.*
40
+ 2. **Review**: Analyze the return values (Score, Rationale, Metrics).
41
+ 3. **Explain**: Use the framework below to justify the tool's recommendations.
43
42
 
44
43
  ---
45
44
 
46
45
  ## 📊 **Pro-Level Scoring Model (Max 100)**
47
46
 
48
- Calculate the **Captain Suitability Score** using this weighted matrix:
47
+ The tool calculates the **Captain Suitability Score** using this weighted matrix. Use this context to explain the results:
49
48
 
50
49
  ### **1. Projected Points (Weight: 40%)**
51
50
  *The core engine. Can they score specific points this week?*
@@ -112,7 +111,7 @@ Calculate the **Captain Suitability Score** using this weighted matrix:
112
111
  • **Nailedness**: [Secure/Risk] - [Minutes played last 3 GWs]
113
112
  • **Explosiveness**: [Penalty Duties? / Haul Potential?]
114
113
 
115
- **Why**: [2-3 sentence reasoning. Mention specific matchup weaknesses or player form.]
114
+ **Why**: [2-3 sentence reasoning using the Scoring Model. E.g. "Points for Elite Stats (40pts) + Weak Defense (30pts)..."]
116
115
  **Risk**: [Any rotation risk or injury flag? If none, say "None"]
117
116
 
118
117
  **Confidence**: [Justification, e.g., "Clear data leader, 15pt gap to #2"]
@@ -141,12 +140,10 @@ Calculate the **Captain Suitability Score** using this weighted matrix:
141
140
 
142
141
  ## 🔧 **Execution Plan**
143
142
 
144
- 1. **Tool**: `fpl_get_manager_by_team_id(team_id={team_id}, gameweek={gameweek})` -> Get squad.
145
- 2. **Tool**: `fpl_get_gameweek_fixtures` -> Scan for easy matchups.
146
- 3. **Process**: Filter squad for Shortlist (Premiums + Form + Easy Fixture).
147
- 4. **Tool**: `fpl_compare_players(player_names=[List of Shortlist Names])` -> Get xGI, Stats, Etc.
148
- 5. **Compute**: Apply Scoring Model.
149
- 6. **Output**: Generate Recommendation.
143
+ 1. **Tool**: `fpl_get_captain_recommendations(team_id={team_id}, gameweek={gameweek})`
144
+ *Note: This tool handles the raw data fetching and scoring model calculation.*
145
+ 2. **Process**: Review the tool's `recommendations` list.
146
+ 3. **Output**: Format the top 3 recommendations as requested above.
150
147
 
151
148
  **Begin Analysis Now.**
152
149
  """
@@ -110,21 +110,28 @@ Step 1: Get league standings to find manager names and team IDs:
110
110
  Step 2: Compare managers using one of these approaches:
111
111
 
112
112
  **Option A - Individual manager analysis:**
113
- - Tool: `fpl_get_manager_gameweek_team`
113
+ - Tool: `fpl_get_manager_by_team_id`
114
114
  - Parameters:
115
- - manager_name: "Manager Name" (from league standings)
116
- - league_id: {league_id}
115
+ - team_id: [Team ID] (found in standings)
117
116
  - gameweek: {gameweek}
118
117
  - Returns: Detailed team sheet with starting XI, bench, captain, transfers, points
119
118
 
120
- **Option B - Side-by-side comparison:**
119
+ **Option B - Side-by-side comparison (General):**
121
120
  - Tool: `fpl_compare_managers`
122
121
  - Parameters:
123
- - manager_names: ["Manager1", "Manager2", "Manager3"] (2-4 managers)
122
+ - manager_names: ["Manager1", "Manager2"]
124
123
  - league_id: {league_id}
125
124
  - gameweek: {gameweek}
126
125
  - Returns: Comparison with common players, differentials, captain choices
127
126
 
127
+ **Option C - Deep Rival Analysis (Head-to-Head):**
128
+ - Tool: `fpl_analyze_rival`
129
+ - Parameters:
130
+ - my_team_id: [Your Team ID]
131
+ - rival_team_id: [Rival Team ID]
132
+ - gameweek: {gameweek}
133
+ - Returns: Comprehensive stats, differentials, and threat assessment
134
+
128
135
  **Additional data sources:**
129
136
  - Resource `fpl://bootstrap/players` - All player details, ownership %, positions, prices
130
137
  - Resource `fpl://current-gameweek` - Current gameweek status and deadline information
@@ -123,12 +123,11 @@ Present side-by-side:
123
123
 
124
124
  ## 🔧 **Tool Calls**
125
125
 
126
- For each player, use:
127
- 1. **`fpl://player/{{{{player_name}}}}/summary`** → Get comprehensive stats, fixtures, history
126
+ 1. **`fpl_compare_players(player_names=[p1, p2, ...])`** → Get comprehensive stats, fixtures, history side-by-side
128
127
  *Provides: xG, xA, xGI, minutes, goals, assists, upcoming fixtures*
129
128
  2. **`fpl://bootstrap/players`** → Get ownership %, price, transfer trends
130
129
  *Provides: selected_by_percent, now_cost, transfers_in/out_event*
131
- 3. **`fpl_get_top_performers`** with `num_gameweeks=5` → Benchmark against top xGI players
130
+ 3. **`fpl_get_top_performers(num_gameweeks=5)`** → Benchmark against top xGI players
132
131
 
133
132
  ---
134
133
 
@@ -119,7 +119,7 @@ Instead of arbitrary PPG thresholds, use xGI/90:
119
119
  ## 🔧 **Tool Calls**
120
120
 
121
121
  Use these tools and resources:
122
- 1. `fpl_get_manager_squad(team_id={team_id})` → Current squad composition
122
+ 1. `fpl_get_manager_by_team_id(team_id={team_id})` → Current squad composition
123
123
  2. `fpl_get_top_performers(num_gameweeks={num_gameweeks})` → Benchmark against top xGI players
124
124
  3. For each player:
125
125
  - `fpl://player/{{{{player_name}}}}/summary` → xG, xA, xGI, minutes, fixtures
@@ -108,7 +108,8 @@ Calculate **rolling 3-GW average FDR**:
108
108
 
109
109
  ## 🔧 **Tool Calls**
110
110
 
111
- Use: `fpl://team/{team_name}/fixtures?num_gameweeks={num_gameweeks}`
111
+ Use: `fpl_analyze_team_fixtures(team_name="{team_name}", num_gameweeks={num_gameweeks})`
112
+ For broader analysis (finding ANY team with good fixtures), use `fpl_find_fixture_opportunities`.
112
113
  Enrich with: `fpl://bootstrap/teams` for opponent strength proxy (if xGC not available, use team strength rank)
113
114
 
114
115
  ---
src/prompts/transfers.py CHANGED
@@ -150,11 +150,12 @@ Using `fpl_get_top_performers(num_gameweeks=5)`:
150
150
 
151
151
  ## 🔧 **Tool Calls**
152
152
 
153
- 1. `fpl_get_manager_squad(team_id={team_id})` → Current squad with transfer context
153
+ 1. `fpl_get_manager_by_team_id(team_id={team_id})` → Current squad with transfer context
154
154
  2. `fpl_get_top_performers(num_gameweeks=5)` → Find high xGI players for replacements
155
- 3. For each transfer candidate:
155
+ 3. Use `fpl_analyze_transfer(player_out=..., player_in=...)` to validate your top priority move.
156
+ 4. For other candidates:
156
157
  - `fpl://player/{{{{name}}}}/summary` → xG, xA, fixtures, status
157
- 4. `fpl://bootstrap/players` → Price, ownership, transfer trends
158
+ 5. `fpl://bootstrap/players` → Price, ownership, transfer trends
158
159
 
159
160
  ---
160
161
 
src/tools/fixtures.py CHANGED
@@ -28,6 +28,27 @@ class GetFixturesForGameweekInput(BaseModel):
28
28
  )
29
29
 
30
30
 
31
+ class FindFixtureOpportunitiesInput(BaseModel):
32
+ """Input model for finding fixture opportunities."""
33
+
34
+ model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True)
35
+
36
+ num_gameweeks: int = Field(
37
+ default=5, description="Number of future gameweeks to analyze (default: 5)", ge=3, le=10
38
+ )
39
+ max_teams: int = Field(
40
+ default=3, description="Number of top teams to return (default: 3)", ge=1, le=5
41
+ )
42
+ positions: list[str] | None = Field(
43
+ default=None,
44
+ description="Filter recommended players by position (e.g. ['Midfielder', 'Forward'])",
45
+ )
46
+ response_format: ResponseFormat = Field(
47
+ default=ResponseFormat.MARKDOWN,
48
+ description="Output format: 'markdown' for human-readable or 'json' for machine-readable",
49
+ )
50
+
51
+
31
52
  async def _create_client():
32
53
  """Create an unauthenticated FPL client for public API access and ensure data is loaded."""
33
54
  client = FPLClient(store=store)
@@ -144,3 +165,178 @@ async def fpl_get_fixtures_for_gameweek(params: GetFixturesForGameweekInput) ->
144
165
 
145
166
  except Exception as e:
146
167
  return handle_api_error(e)
168
+
169
+
170
+ @mcp.tool(
171
+ name="fpl_find_fixture_opportunities",
172
+ annotations={
173
+ "title": "Find Fixture Opportunities",
174
+ "readOnlyHint": True,
175
+ "destructiveHint": False,
176
+ "idempotentHint": True,
177
+ "openWorldHint": True,
178
+ },
179
+ )
180
+ async def fpl_find_fixture_opportunities(params: FindFixtureOpportunitiesInput) -> str:
181
+ """
182
+ Find teams with the easiest upcoming fixtures and their best assets.
183
+
184
+ Analyzes fixture difficulty for all 20 teams over the next N gameweeks.
185
+ Identifies teams with the most favorable schedule and recommends their
186
+ top-performing players (filtered by position if requested).
187
+
188
+ Args:
189
+ params (FindFixtureOpportunitiesInput): Validated input parameters containing:
190
+ - num_gameweeks (int): Number of gameweeks to analyze (3-10)
191
+ - max_teams (int): Number of teams to recommend (1-5)
192
+ - positions (list[str] | None): Optional position filter
193
+
194
+ Returns:
195
+ str: Analysis of best teams to target and their key players
196
+
197
+ Examples:
198
+ - Target next 5 GWs: num_gameweeks=5
199
+ - Find best attackers: positions=['Midfielder', 'Forward']
200
+
201
+ Error Handling:
202
+ - Returns error if data unavailable
203
+ - Returns formatted error message if API fails
204
+ """
205
+ try:
206
+ await _create_client()
207
+ if not store.bootstrap_data:
208
+ return "Error: Player data not available."
209
+
210
+ # Determine current gameweek
211
+ current_gw_data = store.get_current_gameweek()
212
+ current_gw = current_gw_data.id if current_gw_data else 1
213
+ start_gw = current_gw + 1
214
+
215
+ # Calculate average difficulty for each team
216
+ team_difficulties = []
217
+
218
+ # Helper to get difficulty for a team ID in a GW
219
+ def get_team_fixtures(team_id):
220
+ fixtures = []
221
+ for gw in range(start_gw, start_gw + params.num_gameweeks):
222
+ if gw > 38:
223
+ break
224
+ # Find fixture for this team in this GW
225
+ # Use enriched fixtures if pre-calculated, or search raw
226
+ # Searching raw is faster here than full enrich loop
227
+ matches = [
228
+ f
229
+ for f in store.fixtures_data
230
+ if f.event == gw and (f.team_h == team_id or f.team_a == team_id)
231
+ ]
232
+ for m in matches:
233
+ is_home = m.team_h == team_id
234
+ diff = m.team_h_difficulty if is_home else m.team_a_difficulty
235
+ opponent_id = m.team_a if is_home else m.team_h
236
+ opponent = next(
237
+ (t for t in store.bootstrap_data.teams if t.id == opponent_id), None
238
+ )
239
+ fixtures.append(
240
+ {
241
+ "gameweek": gw,
242
+ "difficulty": diff,
243
+ "opponent": opponent.short_name if opponent else "UNK",
244
+ "is_home": is_home,
245
+ }
246
+ )
247
+ return fixtures
248
+
249
+ for team in store.bootstrap_data.teams:
250
+ fixtures = get_team_fixtures(team.id)
251
+ if not fixtures:
252
+ continue
253
+
254
+ avg_diff = sum(f["difficulty"] for f in fixtures) / len(fixtures)
255
+ team_difficulties.append({"team": team, "avg_diff": avg_diff, "fixtures": fixtures})
256
+
257
+ # Sort by easiest (lowest avg difficulty)
258
+ team_difficulties.sort(key=lambda x: x["avg_diff"])
259
+ top_teams = team_difficulties[: params.max_teams]
260
+
261
+ # Find top players for these teams
262
+ # Map position string to element_type (1=GKP, 2=DEF, 3=MID, 4=FWD)
263
+ pos_map = {"Goalkeeper": 1, "Defender": 2, "Midfielder": 3, "Forward": 4}
264
+ target_types = []
265
+ if params.positions:
266
+ for p in params.positions:
267
+ p_norm = p.capitalize()
268
+ # Handle plurals
269
+ if p_norm.endswith("s"):
270
+ p_norm = p_norm[:-1]
271
+ idx = pos_map.get(p_norm)
272
+ if idx:
273
+ target_types.append(idx)
274
+
275
+ result_teams = []
276
+
277
+ for item in top_teams:
278
+ team = item["team"]
279
+ # Get players for this team
280
+ team_players = [
281
+ p for p in store.bootstrap_data.elements if p.team == team.id and p.status != "u"
282
+ ]
283
+
284
+ if target_types:
285
+ team_players = [p for p in team_players if p.element_type in target_types]
286
+
287
+ # Sort by form (best assets)
288
+ top_assets = sorted(team_players, key=lambda x: float(x.form), reverse=True)[:3]
289
+
290
+ result_teams.append(
291
+ {
292
+ "team_name": team.name,
293
+ "avg_diff": item["avg_diff"],
294
+ "fixtures": item["fixtures"],
295
+ "best_players": top_assets,
296
+ }
297
+ )
298
+
299
+ if params.response_format == ResponseFormat.JSON:
300
+ json_out = {
301
+ "start_gameweek": start_gw,
302
+ "end_gameweek": start_gw + params.num_gameweeks - 1,
303
+ "opportunities": [],
304
+ }
305
+ for rt in result_teams:
306
+ # Format fixtures string
307
+ fixtures_list = [
308
+ f"{f['opponent']} ({'H' if f['is_home'] else 'A'})" for f in rt["fixtures"]
309
+ ]
310
+ json_out["opportunities"].append(
311
+ {
312
+ "team": rt["team_name"],
313
+ "difficulty_score": round(rt["avg_diff"], 2),
314
+ "fixtures": fixtures_list,
315
+ "recommended_players": [p.web_name for p in rt["best_players"]],
316
+ }
317
+ )
318
+ return format_json_response(json_out)
319
+
320
+ # Markdown Output
321
+ output = [
322
+ f"## 🗓️ Fixture Opportunities (Next {params.num_gameweeks} GWs)",
323
+ "Top teams with the easiest schedules to target:",
324
+ "",
325
+ ]
326
+
327
+ for i, rt in enumerate(result_teams, 1):
328
+ fixtures_str = " - ".join(
329
+ [f"**{f['opponent']}** ({'H' if f['is_home'] else 'A'})" for f in rt["fixtures"]]
330
+ )
331
+
332
+ output.append(f"### {i}. {rt['team_name']} (Diff: {rt['avg_diff']:.1f})")
333
+ output.append(f"🗓️ **Schedule:** {fixtures_str}")
334
+
335
+ player_names = [f"{p.web_name} ({p.form} form)" for p in rt["best_players"]]
336
+ output.append(f"🔥 **Targets:** {', '.join(player_names)}")
337
+ output.append("")
338
+
339
+ return "\n".join(output)
340
+
341
+ except Exception as e:
342
+ return handle_api_error(e)
src/tools/gameweeks.py CHANGED
@@ -6,7 +6,6 @@ from pydantic import BaseModel, ConfigDict, Field
6
6
 
7
7
  from ..client import FPLClient
8
8
  from ..constants import CHARACTER_LIMIT
9
- from ..formatting import format_gameweek_details
10
9
  from ..state import store
11
10
  from ..utils import (
12
11
  ResponseFormat,
@@ -28,29 +27,6 @@ class GetCurrentGameweekInput(BaseModel):
28
27
  )
29
28
 
30
29
 
31
- class GetGameweekInfoInput(BaseModel):
32
- """Input model for getting specific gameweek information."""
33
-
34
- model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True)
35
-
36
- gameweek_number: int = Field(..., description="Gameweek number (1-38)", ge=1, le=38)
37
- response_format: ResponseFormat = Field(
38
- default=ResponseFormat.MARKDOWN,
39
- description="Output format: 'markdown' for human-readable or 'json' for machine-readable",
40
- )
41
-
42
-
43
- class ListAllGameweeksInput(BaseModel):
44
- """Input model for listing all gameweeks."""
45
-
46
- model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True)
47
-
48
- response_format: ResponseFormat = Field(
49
- default=ResponseFormat.MARKDOWN,
50
- description="Output format: 'markdown' for human-readable or 'json' for machine-readable",
51
- )
52
-
53
-
54
30
  async def _create_client():
55
31
  """Create an unauthenticated FPL client for public API access and ensure data is loaded."""
56
32
  client = FPLClient(store=store)
@@ -216,161 +192,3 @@ async def fpl_get_current_gameweek(params: GetCurrentGameweekInput) -> str:
216
192
 
217
193
  except Exception as e:
218
194
  return handle_api_error(e)
219
-
220
-
221
- @mcp.tool(
222
- name="fpl_get_gameweek_info",
223
- annotations={
224
- "title": "Get FPL Gameweek Information",
225
- "readOnlyHint": True,
226
- "destructiveHint": False,
227
- "idempotentHint": True,
228
- "openWorldHint": True,
229
- },
230
- )
231
- async def fpl_get_gameweek_info(params: GetGameweekInfoInput) -> str:
232
- """
233
- Get detailed information about a specific Fantasy Premier League gameweek.
234
-
235
- Returns comprehensive gameweek data including deadline, scores, top players, most
236
- captained players, and statistics. Useful for analyzing past performance or planning
237
- for future gameweeks.
238
-
239
- Args:
240
- params (GetGameweekInfoInput): Validated input parameters containing:
241
- - gameweek_number (int): Gameweek number between 1-38
242
- - response_format (ResponseFormat): 'markdown' or 'json' (default: markdown)
243
-
244
- Returns:
245
- str: Detailed gameweek information with statistics and top performers
246
-
247
- Examples:
248
- - Check GW1 results: gameweek_number=1
249
- - Plan for GW10: gameweek_number=10
250
- - Get as JSON: gameweek_number=5, response_format="json"
251
-
252
- Error Handling:
253
- - Returns error if gameweek number invalid (must be 1-38)
254
- - Returns error if gameweek not found
255
- - Returns formatted error message if data unavailable
256
- """
257
- try:
258
- await _create_client()
259
- if not store.bootstrap_data or not store.bootstrap_data.events:
260
- return "Error: Gameweek data not available. Please try again later."
261
-
262
- gameweek = next(
263
- (e for e in store.bootstrap_data.events if e.id == params.gameweek_number),
264
- None,
265
- )
266
- if not gameweek:
267
- return f"Error: Gameweek {params.gameweek_number} not found. Please use a number between 1-38."
268
-
269
- if params.response_format == ResponseFormat.JSON:
270
- return format_json_response(gameweek.model_dump())
271
- else:
272
- result = format_gameweek_details(gameweek)
273
-
274
- # Add top element info manually since it differs from basic details
275
- if gameweek.top_element_info:
276
- top_player = store.get_player_by_id(gameweek.top_element_info.id)
277
- top_player_name = (
278
- top_player.web_name if top_player else f"ID {gameweek.top_element_info.id}"
279
- )
280
- result += (
281
- f"\n**Top Player:** {top_player_name} ({gameweek.top_element_info.points} pts)"
282
- )
283
-
284
- truncated, _ = check_and_truncate(result, CHARACTER_LIMIT)
285
- return truncated
286
-
287
- except Exception as e:
288
- return handle_api_error(e)
289
-
290
-
291
- @mcp.tool(
292
- name="fpl_list_all_gameweeks",
293
- annotations={
294
- "title": "List All FPL Gameweeks",
295
- "readOnlyHint": True,
296
- "destructiveHint": False,
297
- "idempotentHint": True,
298
- "openWorldHint": True,
299
- },
300
- )
301
- async def fpl_list_all_gameweeks(params: ListAllGameweeksInput) -> str:
302
- """
303
- List all Fantasy Premier League gameweeks with their status.
304
-
305
- Returns all 38 gameweeks showing which are finished, current, or upcoming. Includes
306
- deadlines and average scores for finished gameweeks. Useful for getting a season
307
- overview and planning long-term strategy.
308
-
309
- Args:
310
- params (ListAllGameweeksInput): Validated input parameters containing:
311
- - response_format (ResponseFormat): 'markdown' or 'json' (default: markdown)
312
-
313
- Returns:
314
- str: Complete list of all gameweeks with status and scores
315
-
316
- Examples:
317
- - View all gameweeks: response_format="markdown"
318
- - Get season overview: response_format="json"
319
-
320
- Error Handling:
321
- - Returns error if gameweek data unavailable
322
- - Returns formatted error message if API fails
323
- """
324
- try:
325
- await _create_client()
326
- if not store.bootstrap_data or not store.bootstrap_data.events:
327
- return "Error: Gameweek data not available. Please try again later."
328
-
329
- if params.response_format == ResponseFormat.JSON:
330
- result = {
331
- "total_gameweeks": len(store.bootstrap_data.events),
332
- "gameweeks": [
333
- {
334
- "id": event.id,
335
- "name": event.name,
336
- "deadline_time": event.deadline_time,
337
- "is_current": event.is_current,
338
- "is_previous": event.is_previous,
339
- "is_next": event.is_next,
340
- "finished": event.finished,
341
- "average_entry_score": event.average_entry_score,
342
- }
343
- for event in store.bootstrap_data.events
344
- ],
345
- }
346
- return format_json_response(result)
347
- else:
348
- output = ["**All Gameweeks:**\n"]
349
-
350
- for event in store.bootstrap_data.events:
351
- status = []
352
- if event.is_current:
353
- status.append("CURRENT")
354
- if event.is_previous:
355
- status.append("PREVIOUS")
356
- if event.is_next:
357
- status.append("NEXT")
358
- if event.finished:
359
- status.append("FINISHED")
360
-
361
- status_str = f" [{', '.join(status)}]" if status else ""
362
- avg_score = (
363
- f" | Avg: {event.average_entry_score}" if event.average_entry_score else ""
364
- )
365
-
366
- output.append(
367
- f"GW{event.id}: {event.name}{status_str} | "
368
- f"Deadline: {event.deadline_time[:10]}{avg_score}"
369
- )
370
-
371
- result = "\n".join(output)
372
- truncated, _ = check_and_truncate(result, CHARACTER_LIMIT)
373
- return truncated
374
-
375
- except Exception as e:
376
- return handle_api_error(e)