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.
- fpl_mcp_server-0.1.3.dist-info/METADATA +137 -0
- fpl_mcp_server-0.1.3.dist-info/RECORD +33 -0
- fpl_mcp_server-0.1.3.dist-info/WHEEL +4 -0
- fpl_mcp_server-0.1.3.dist-info/entry_points.txt +2 -0
- fpl_mcp_server-0.1.3.dist-info/licenses/LICENSE +21 -0
- src/cache.py +162 -0
- src/client.py +273 -0
- src/config.py +33 -0
- src/constants.py +118 -0
- src/exceptions.py +114 -0
- src/formatting.py +299 -0
- src/main.py +41 -0
- src/models.py +526 -0
- src/prompts/__init__.py +18 -0
- src/prompts/chips.py +127 -0
- src/prompts/league_analysis.py +250 -0
- src/prompts/player_analysis.py +141 -0
- src/prompts/squad_analysis.py +136 -0
- src/prompts/team_analysis.py +121 -0
- src/prompts/transfers.py +167 -0
- src/rate_limiter.py +101 -0
- src/resources/__init__.py +13 -0
- src/resources/bootstrap.py +183 -0
- src/state.py +443 -0
- src/tools/__init__.py +25 -0
- src/tools/fixtures.py +162 -0
- src/tools/gameweeks.py +392 -0
- src/tools/leagues.py +590 -0
- src/tools/players.py +840 -0
- src/tools/teams.py +397 -0
- src/tools/transfers.py +629 -0
- src/utils.py +226 -0
- src/validators.py +290 -0
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")
|
src/prompts/__init__.py
ADDED
|
@@ -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%)"""
|