fpl-mcp-server 0.1.3__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/models.py ADDED
@@ -0,0 +1,526 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ class Player(BaseModel):
7
+ id: int
8
+ web_name: str
9
+ first_name: str
10
+ second_name: str
11
+ team: int
12
+ element_type: int
13
+ now_cost: int
14
+ form: str
15
+ points_per_game: str
16
+ news: str
17
+
18
+ # Computed fields
19
+ team_name: str | None = None
20
+ position: str | None = None
21
+ price: float = Field(default=0.0)
22
+
23
+ def __init__(self, **data):
24
+ super().__init__(**data)
25
+ self.price = self.now_cost / 10
26
+
27
+
28
+ class ElementData(BaseModel):
29
+ """Player element from bootstrap data"""
30
+
31
+ id: int
32
+ web_name: str
33
+ first_name: str
34
+ second_name: str
35
+ team: int
36
+ element_type: int
37
+ now_cost: int
38
+ form: str
39
+ points_per_game: str
40
+ news: str
41
+ status: str
42
+
43
+ # Enriched fields (added during bootstrap loading)
44
+ team_name: str | None = None
45
+ position: str | None = None
46
+
47
+ # Allow extra fields from the API that we don't need to validate
48
+ model_config = ConfigDict(extra="allow")
49
+
50
+
51
+ class TeamData(BaseModel):
52
+ """Team data from bootstrap"""
53
+
54
+ id: int
55
+ name: str
56
+ short_name: str
57
+
58
+ model_config = ConfigDict(extra="allow")
59
+
60
+
61
+ class ElementTypeData(BaseModel):
62
+ """Position type data from bootstrap"""
63
+
64
+ id: int
65
+ singular_name_short: str
66
+ plural_name_short: str
67
+
68
+ model_config = ConfigDict(extra="allow")
69
+
70
+
71
+ class TopElementInfo(BaseModel):
72
+ """Top scoring player info for an event"""
73
+
74
+ id: int
75
+ points: int
76
+
77
+
78
+ class EventData(BaseModel):
79
+ """Gameweek event data from bootstrap"""
80
+
81
+ id: int
82
+ name: str
83
+ deadline_time: str
84
+ average_entry_score: int | None = None
85
+ finished: bool
86
+ data_checked: bool
87
+ highest_scoring_entry: int | None = None
88
+ deadline_time_epoch: int
89
+ highest_score: int | None = None
90
+ is_previous: bool
91
+ is_current: bool
92
+ is_next: bool
93
+ can_enter: bool
94
+ released: bool
95
+ top_element: int | None = None
96
+ top_element_info: TopElementInfo | None = None
97
+ most_selected: int | None = None
98
+ most_transferred_in: int | None = None
99
+ most_captained: int | None = None
100
+ most_vice_captained: int | None = None
101
+
102
+ model_config = ConfigDict(extra="allow")
103
+
104
+
105
+ class BootstrapData(BaseModel):
106
+ """Bootstrap static data structure"""
107
+
108
+ elements: list[ElementData]
109
+ teams: list[TeamData]
110
+ element_types: list[ElementTypeData]
111
+ events: list[EventData]
112
+
113
+ model_config = ConfigDict(extra="allow")
114
+
115
+
116
+ class TransferPayload(BaseModel):
117
+ chip: str | None = None
118
+ entry: int
119
+ event: int
120
+ transfers: list[dict[str, int]]
121
+ wildcard: bool = False
122
+ freehit: bool = False
123
+
124
+
125
+ class FixtureStatValue(BaseModel):
126
+ """Individual stat value in a fixture"""
127
+
128
+ value: int
129
+ element: int
130
+
131
+
132
+ class FixtureStat(BaseModel):
133
+ """Stat category in a fixture"""
134
+
135
+ identifier: str
136
+ a: list[FixtureStatValue]
137
+ h: list[FixtureStatValue]
138
+
139
+
140
+ class FixtureData(BaseModel):
141
+ """Fixture data from the fixtures endpoint"""
142
+
143
+ code: int
144
+ event: int | None = None
145
+ finished: bool
146
+ finished_provisional: bool
147
+ id: int
148
+ kickoff_time: str | None = None
149
+ minutes: int
150
+ provisional_start_time: bool
151
+ started: bool
152
+ team_a: int
153
+ team_a_score: int | None = None
154
+ team_h: int
155
+ team_h_score: int | None = None
156
+ stats: list[FixtureStat]
157
+ team_h_difficulty: int
158
+ team_a_difficulty: int
159
+ pulse_id: int
160
+
161
+ model_config = ConfigDict(extra="allow")
162
+
163
+
164
+ # Models for element-summary endpoint (player details)
165
+
166
+
167
+ class PlayerFixture(BaseModel):
168
+ """Fixture information for a player"""
169
+
170
+ id: int
171
+ code: int
172
+ team_h: int
173
+ team_h_score: int | None = None
174
+ team_a: int
175
+ team_a_score: int | None = None
176
+ event: int | None = None
177
+ finished: bool
178
+ minutes: int
179
+ provisional_start_time: bool
180
+ kickoff_time: str | None = None
181
+ event_name: str
182
+ is_home: bool
183
+ difficulty: int
184
+
185
+
186
+ class PlayerHistory(BaseModel):
187
+ """Historical performance data for a player in a gameweek"""
188
+
189
+ element: int
190
+ fixture: int
191
+ opponent_team: int
192
+ total_points: int
193
+ was_home: bool
194
+ kickoff_time: str
195
+ team_h_score: int | None = None
196
+ team_a_score: int | None = None
197
+ round: int
198
+ modified: bool
199
+ minutes: int
200
+ goals_scored: int
201
+ assists: int
202
+ clean_sheets: int
203
+ goals_conceded: int
204
+ own_goals: int
205
+ penalties_saved: int
206
+ penalties_missed: int
207
+ yellow_cards: int
208
+ red_cards: int
209
+ saves: int
210
+ bonus: int
211
+ bps: int
212
+ influence: str
213
+ creativity: str
214
+ threat: str
215
+ ict_index: str
216
+ starts: int
217
+ expected_goals: str
218
+ expected_assists: str
219
+ expected_goal_involvements: str
220
+ expected_goals_conceded: str
221
+ value: int
222
+ transfers_balance: int
223
+ selected: int
224
+ transfers_in: int
225
+ transfers_out: int
226
+
227
+ model_config = ConfigDict(extra="allow")
228
+
229
+
230
+ class PlayerHistoryPast(BaseModel):
231
+ """Historical season data for a player"""
232
+
233
+ season_name: str
234
+ element_code: int
235
+ start_cost: int
236
+ end_cost: int
237
+ total_points: int
238
+ minutes: int
239
+ goals_scored: int
240
+ assists: int
241
+ clean_sheets: int
242
+ goals_conceded: int
243
+ own_goals: int
244
+ penalties_saved: int
245
+ penalties_missed: int
246
+ yellow_cards: int
247
+ red_cards: int
248
+ saves: int
249
+ bonus: int
250
+ bps: int
251
+ influence: str
252
+ creativity: str
253
+ threat: str
254
+ ict_index: str
255
+ starts: int
256
+ expected_goals: str
257
+ expected_assists: str
258
+ expected_goal_involvements: str
259
+ expected_goals_conceded: str
260
+
261
+ model_config = ConfigDict(extra="allow")
262
+
263
+
264
+ class ElementSummary(BaseModel):
265
+ """Complete player summary from element-summary endpoint"""
266
+
267
+ fixtures: list[PlayerFixture]
268
+ history: list[PlayerHistory]
269
+ history_past: list[PlayerHistoryPast]
270
+
271
+
272
+ # Models for entry endpoint (FPL manager/team info)
273
+
274
+
275
+ class LeaguePhase(BaseModel):
276
+ """Phase information within a league"""
277
+
278
+ phase: int
279
+ rank: int
280
+ last_rank: int
281
+ rank_sort: int
282
+ total: int
283
+ league_id: int
284
+ rank_count: int | None = None
285
+ entry_percentile_rank: int | None = None
286
+
287
+
288
+ class ClassicLeague(BaseModel):
289
+ """Classic league information for a manager"""
290
+
291
+ id: int
292
+ name: str
293
+ short_name: str | None = None
294
+ created: str
295
+ closed: bool
296
+ rank: int | None = None
297
+ max_entries: int | None = None
298
+ league_type: str
299
+ scoring: str
300
+ admin_entry: int | None = None
301
+ start_event: int
302
+ entry_can_leave: bool
303
+ entry_can_admin: bool
304
+ entry_can_invite: bool
305
+ has_cup: bool
306
+ cup_league: int | None = None
307
+ cup_qualified: bool | None = None
308
+ rank_count: int | None = None
309
+ entry_percentile_rank: int | None = None
310
+ active_phases: list[LeaguePhase]
311
+ entry_rank: int
312
+ entry_last_rank: int
313
+
314
+
315
+ class CupStatus(BaseModel):
316
+ """Cup qualification status"""
317
+
318
+ qualification_event: int | None = None
319
+ qualification_numbers: int | None = None
320
+ qualification_rank: int | None = None
321
+ qualification_state: str | None = None
322
+
323
+
324
+ class Cup(BaseModel):
325
+ """Cup information for a manager"""
326
+
327
+ matches: list[Any]
328
+ status: CupStatus
329
+ cup_league: int | None = None
330
+
331
+
332
+ class Leagues(BaseModel):
333
+ """All leagues information for a manager"""
334
+
335
+ classic: list[ClassicLeague]
336
+ h2h: list[Any]
337
+ cup: Cup
338
+ cup_matches: list[Any]
339
+
340
+
341
+ class ManagerEntry(BaseModel):
342
+ """FPL manager/team entry information"""
343
+
344
+ id: int
345
+ joined_time: str
346
+ started_event: int
347
+ favourite_team: int | None = None
348
+ player_first_name: str
349
+ player_last_name: str
350
+ player_region_id: int
351
+ player_region_name: str
352
+ player_region_iso_code_short: str
353
+ player_region_iso_code_long: str
354
+ years_active: int
355
+ summary_overall_points: int
356
+ summary_overall_rank: int
357
+ summary_event_points: int
358
+ summary_event_rank: int
359
+ current_event: int
360
+ leagues: Leagues
361
+ name: str
362
+ name_change_blocked: bool
363
+ entered_events: list[int]
364
+ kit: str | None = None
365
+ last_deadline_bank: int
366
+ last_deadline_value: int
367
+ last_deadline_total_transfers: int
368
+ club_badge_src: str | None = None
369
+
370
+ model_config = ConfigDict(extra="allow")
371
+
372
+
373
+ # Models for league standings endpoint
374
+
375
+
376
+ class LeagueStandingEntry(BaseModel):
377
+ """Individual entry in league standings"""
378
+
379
+ id: int
380
+ event_total: int
381
+ player_name: str
382
+ rank: int
383
+ last_rank: int
384
+ rank_sort: int
385
+ total: int
386
+ entry: int
387
+ entry_name: str
388
+
389
+ model_config = ConfigDict(extra="allow")
390
+
391
+
392
+ class LeagueStandings(BaseModel):
393
+ """League standings response"""
394
+
395
+ has_next: bool
396
+ page: int
397
+ results: list[LeagueStandingEntry]
398
+
399
+ model_config = ConfigDict(extra="allow")
400
+
401
+
402
+ class LeagueStandingsResponse(BaseModel):
403
+ """Complete league standings response with league info"""
404
+
405
+ league: ClassicLeague
406
+ standings: LeagueStandings
407
+
408
+ model_config = ConfigDict(extra="allow")
409
+
410
+
411
+ # Models for manager gameweek picks endpoint
412
+
413
+
414
+ class AutomaticSub(BaseModel):
415
+ """Automatic substitution made during a gameweek"""
416
+
417
+ entry: int
418
+ element_in: int
419
+ element_out: int
420
+ event: int
421
+
422
+
423
+ class PickElement(BaseModel):
424
+ """Individual player pick in a gameweek team"""
425
+
426
+ element: int
427
+ position: int
428
+ multiplier: int
429
+ is_captain: bool
430
+ is_vice_captain: bool
431
+
432
+ model_config = ConfigDict(extra="allow")
433
+
434
+
435
+ # Models for /me endpoint (current user info)
436
+
437
+
438
+ class UserPlayer(BaseModel):
439
+ """Current user's player information from /me endpoint"""
440
+
441
+ first_name: str
442
+ last_name: str
443
+ email: str
444
+ entry: int # This is the user's team ID
445
+ region: int
446
+ id: int # Player ID (not team ID)
447
+
448
+ model_config = ConfigDict(extra="allow")
449
+
450
+
451
+ class MeResponse(BaseModel):
452
+ """Response from /me endpoint"""
453
+
454
+ player: UserPlayer
455
+ watched: list[Any]
456
+
457
+
458
+ class EntryHistory(BaseModel):
459
+ """Manager's performance for a specific gameweek"""
460
+
461
+ event: int
462
+ points: int
463
+ total_points: int
464
+ rank: int | None = None
465
+ rank_sort: int | None = None
466
+ overall_rank: int
467
+ bank: int
468
+ value: int
469
+ event_transfers: int
470
+ event_transfers_cost: int
471
+ points_on_bench: int
472
+
473
+ model_config = ConfigDict(extra="allow")
474
+
475
+
476
+ class GameweekPicks(BaseModel):
477
+ """Manager's team picks for a specific gameweek"""
478
+
479
+ active_chip: str | None = None
480
+ automatic_subs: list[AutomaticSub]
481
+ entry_history: EntryHistory
482
+ picks: list[PickElement]
483
+
484
+ model_config = ConfigDict(extra="allow")
485
+
486
+
487
+ # Models for my-team endpoint (includes chips and transfers)
488
+
489
+
490
+ class ChipData(BaseModel):
491
+ """Chip information from my-team endpoint"""
492
+
493
+ id: int
494
+ status_for_entry: str # "available", "played", "unavailable"
495
+ played_by_entry: list[int] # List of gameweeks where chip was played
496
+ name: str # "wildcard", "freehit", "bboost", "3xc"
497
+ number: int # Chip number (1 or 2 for first/second half)
498
+ start_event: int # First gameweek chip can be used
499
+ stop_event: int # Last gameweek chip can be used
500
+ chip_type: str # "team" or "transfer"
501
+ is_pending: bool
502
+
503
+ model_config = ConfigDict(extra="allow")
504
+
505
+
506
+ class TransfersData(BaseModel):
507
+ """Transfer information from my-team endpoint"""
508
+
509
+ cost: int # Points cost for transfers
510
+ status: str # "cost" or other status
511
+ limit: int # Maximum number of free transfers that can accumulate
512
+ made: int # Number of transfers made this gameweek
513
+ bank: int # Money in bank (in tenths, divide by 10 for actual value)
514
+ value: int # Total squad value (in tenths, divide by 10 for actual value)
515
+
516
+ model_config = ConfigDict(extra="allow")
517
+
518
+
519
+ class MyTeamResponse(BaseModel):
520
+ """Complete response from my-team endpoint"""
521
+
522
+ picks: list[PickElement]
523
+ chips: list[ChipData]
524
+ transfers: TransfersData
525
+
526
+ model_config = ConfigDict(extra="allow")
@@ -0,0 +1,18 @@
1
+ """FPL MCP Prompts - Topic-based modules."""
2
+ # ruff: noqa: E402
3
+
4
+ # Import shared MCP instance from tools
5
+ from ..tools import mcp
6
+
7
+ # Import all prompt modules (this registers prompts with mcp) # noqa: E402
8
+ from . import (
9
+ chips, # noqa: F401
10
+ league_analysis, # noqa: F401
11
+ player_analysis, # noqa: F401
12
+ squad_analysis, # noqa: F401
13
+ team_analysis, # noqa: F401
14
+ transfers, # noqa: F401
15
+ )
16
+
17
+ # Re-export mcp instance
18
+ __all__ = ["mcp"]
src/prompts/chips.py ADDED
@@ -0,0 +1,127 @@
1
+ """
2
+ FPL MCP Prompts - Chip Strategy Recommendations.
3
+
4
+ Prompts guide the LLM in analyzing chip strategies and timing
5
+ for optimal gameweek selection using quantitative analysis.
6
+ """
7
+
8
+ from ..tools import mcp
9
+
10
+
11
+ @mcp.prompt()
12
+ def recommend_chip_strategy(team_id: int) -> str:
13
+ """
14
+ Analyze chip strategy with xGI-based metrics and EV calculations.
15
+
16
+ This prompt guides the LLM to recommend optimal chip timing using
17
+ quantitative expected value analysis rather than subjective assessments.
18
+
19
+ Args:
20
+ team_id: FPL team ID of the manager to analyze
21
+ """
22
+ return f"""Analyze available chips for team ID {team_id} and recommend optimal timing strategy.
23
+
24
+ **Chip Analysis Framework:**
25
+
26
+ For each available chip, provide strategic recommendations:
27
+
28
+ 1. **๐Ÿƒ WILDCARD Strategy:**
29
+ - **Optimal Timing**: Use 1 GW before Double Gameweeks (DGW)
30
+ - **Squad Health Check**: Count injured/unavailable players
31
+ - **Trigger Points**:
32
+ * DGW detected within next 5 gameweeks โ†’ HIGH priority
33
+ * 3+ players injured/unavailable โ†’ HIGH priority
34
+ * Major squad overhaul needed (5+ underperformers with xGI/90 <0.2) โ†’ MEDIUM priority
35
+ - **Pro Tip**: Maximize new players' potential by wildcarding before DGW
36
+
37
+ 2. **๐ŸŽฏ FREE HIT Strategy:**
38
+ - **Optimal Timing**: Save for Blank Gameweeks (BGW)
39
+ - **BGW Detection**: When <60% of teams play (typically <12 teams)
40
+ - **BGW Scarcity Modeling**:
41
+ * BGW with 4-6 teams playing โ†’ EXTREME value (limited options) โ†’ HIGH priority
42
+ * BGW with 8-12 teams playing โ†’ MODERATE value โ†’ MEDIUM priority
43
+ * DGW without BGW โ†’ LOW priority (backup option only)
44
+ - **Pro Tip**: Best used when few teams play, allows one-week team transformation
45
+
46
+ 3. **โญ TRIPLE CAPTAIN Strategy:**
47
+ - **Optimal Timing**: Premium players (ยฃ9m+) in DGW
48
+ - **Analysis Required (xGI-Based)**:
49
+ * Identify premium players in squad
50
+ * For each premium, calculate **TC Expected Points**:
51
+ - Base: xGI/90 ร— 180 minutes (DGW) = Expected goal involvements
52
+ - Convert xGI to points: (xGI ร— 6 avg pts per involvement)
53
+ - Multiply by 3 (captain) = TC Expected Points
54
+ * Add fixture bonus:
55
+ - Easy fixtures (FDR <2.5): +20%
56
+ - Home fixtures: +15%
57
+ - **Trigger Points**:
58
+ * Premium has DGW + xGI/90 >0.7 โ†’ HIGH priority (expect 30+ TC pts)
59
+ * Premium has easy home fixture (single GW) + xGI/90 >0.8 โ†’ MEDIUM priority (expect 15+ TC pts)
60
+ * No premiums with xGI/90 >0.6 โ†’ LOW priority
61
+ - **Pro Tip**: Maximum impact on high xGI players in double gameweeks
62
+
63
+ 4. **๐Ÿ“Š BENCH BOOST Strategy:**
64
+ - **Optimal Timing**: When bench players have DGW
65
+
66
+ **Bench Quality Check (xGI-Based):**
67
+ For each bench player:
68
+ - **xGI/90**: Expected output per 90 minutes
69
+ - **Minutes Security**: % of minutes played last 5 GW (need >60% for reliability)
70
+ - **Expected Points (DGW)**:
71
+ * Attackers/Mids: (xGI/90 ร— 180 mins DGW รท 90) ร— 6 pts per involvement + 4 pts (2ร— appearance)
72
+ * Defenders: (xGI/90 ร— 180 รท 90) ร— 6 + Clean Sheet Odds ร— 8 pts (2 CS ร— 4pts) + 4 pts
73
+ * GKP: Clean Sheet Odds ร— 8 pts + 4 pts (2ร— appearance)
74
+
75
+ **Bench Boost EV Calculation:**
76
+ ```
77
+ Total Bench EV = Sum of 4 bench players' expected points
78
+ ```
79
+
80
+ **Trigger Points**:
81
+ - **Total Bench EV >25 pts in DGW** โ†’ HIGH priority (excellent bench boost)
82
+ - **Total Bench EV 18-25 pts** โ†’ MEDIUM priority (good bench boost)
83
+ - **Total Bench EV <18 pts** โ†’ LOW priority (weak bench, improve first)
84
+ - **2+ bench players have DGW** โ†’ Required for consideration
85
+
86
+ **Bench Improvement Suggestions** (if weak):
87
+ - Target players with xGI/90 >0.15 under ยฃ5.5m
88
+ - Prioritize nailed-on starters (minutes >70%) in teams with upcoming DGW
89
+
90
+ - **Pro Tip**: Maximize returns when bench has DGW + strong xGI output
91
+
92
+ **Fixture Analysis (Next 10 Gameweeks):**
93
+ Scan for:
94
+ - **Double Gameweeks (DGW)**: Teams playing twice in one GW
95
+ - **Blank Gameweeks (BGW)**: <60% of teams playing
96
+ * Count teams playing each GW to assess scarcity
97
+ - Pattern recognition for optimal chip timing
98
+
99
+ **Priority Ranking:**
100
+ Sort recommendations by:
101
+ - ๐Ÿ”ด HIGH: Immediate opportunity or urgent need (DGW within 3 GW, bench boost EV >25)
102
+ - ๐ŸŸก MEDIUM: Good opportunity within 5 gameweeks (bench boost EV 18-25)
103
+ - ๐ŸŸข LOW: No immediate opportunity, save for later (wait for DGW/BGW)
104
+
105
+ **Data Access:**
106
+ Use these tools and resources to gather chip and squad data:
107
+ - Tool `fpl_get_manager_chips` with team_id={team_id} - Get used and available chips with timing
108
+ - Resource `fpl://manager/{{{team_id}}}/chips` - Chip usage summary
109
+ - Tool `fpl_get_manager_squad` with team_id={team_id} - Squad composition for chip analysis
110
+ - Resource `fpl://current-gameweek` - Current gameweek info
111
+ - Resource `fpl://gameweek/{{{{gw}}}}/fixtures` - Fixtures for each upcoming GW to detect DGWs/BGWs
112
+ - Resource `fpl://player/{{{{player_name}}}}/summary` - Premium player xGI analysis
113
+
114
+ **Output Format:**
115
+ 1. Available chips list
116
+ 2. For each chip:
117
+ - Strategic recommendation
118
+ - Specific gameweek suggestion (if applicable)
119
+ - **Quantitative Justification**:
120
+ * Triple Captain: Expected TC points
121
+ * Bench Boost: Total Bench EV calculation
122
+ - Priority level with reasoning
123
+ - Pro tip
124
+ 3. Upcoming fixture overview (next 6 GWs)
125
+ - Highlight DGWs and BGWs
126
+ - Team counts per gameweek (for BGW scarcity assessment)
127
+ 4. If Bench Boost weak: Specific improvement targets (players with xGI/90 >0.15, <ยฃ5.5m, minutes >70%)"""