fpl-mcp-server 0.1.4__py3-none-any.whl → 0.1.6__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.4.dist-info → fpl_mcp_server-0.1.6.dist-info}/METADATA +67 -58
- fpl_mcp_server-0.1.6.dist-info/RECORD +35 -0
- src/client.py +31 -14
- src/constants.py +4 -88
- src/models.py +15 -30
- src/prompts/__init__.py +2 -0
- src/prompts/captain_recommendation.py +152 -0
- src/prompts/team_selection.py +105 -0
- src/resources/bootstrap.py +7 -1
- src/state.py +10 -4
- src/tools/__init__.py +0 -2
- src/tools/fixtures.py +0 -16
- src/tools/gameweeks.py +0 -16
- src/tools/leagues.py +178 -14
- src/tools/players.py +7 -18
- src/tools/teams.py +0 -16
- src/tools/transfers.py +0 -16
- fpl_mcp_server-0.1.4.dist-info/RECORD +0 -33
- {fpl_mcp_server-0.1.4.dist-info → fpl_mcp_server-0.1.6.dist-info}/WHEEL +0 -0
- {fpl_mcp_server-0.1.4.dist-info → fpl_mcp_server-0.1.6.dist-info}/entry_points.txt +0 -0
- {fpl_mcp_server-0.1.4.dist-info → fpl_mcp_server-0.1.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fpl-mcp-server
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
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
|
|
@@ -29,81 +29,100 @@ Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
|
29
29
|
Requires-Dist: ruff>=0.14.0; extra == 'dev'
|
|
30
30
|
Description-Content-Type: text/markdown
|
|
31
31
|
|
|
32
|
-
#
|
|
32
|
+
# Fantasy Premier League MCP Server
|
|
33
33
|
|
|
34
34
|
A comprehensive **Model Context Protocol (MCP)** server for Fantasy Premier League analysis and strategy. This server provides AI assistants with powerful tools, resources, and prompts to help you dominate your FPL mini-leagues with data-driven insights.
|
|
35
35
|
|
|
36
36
|
[](https://opensource.org/licenses/MIT)
|
|
37
|
-
[](https://www.python.org/downloads/)
|
|
38
38
|
[](https://modelcontextprotocol.io)
|
|
39
39
|
|
|
40
|
-
##
|
|
40
|
+
## Features
|
|
41
41
|
|
|
42
42
|
This MCP server provides comprehensive FPL analysis capabilities through:
|
|
43
43
|
|
|
44
|
-
- **
|
|
45
|
-
- **
|
|
46
|
-
- **
|
|
44
|
+
- **22 Interactive Tools** - Search players, analyze fixtures, compare managers, track transfers, and more
|
|
45
|
+
- **4 Data Resources** - access to players, teams, and gameweeks bootstrap data
|
|
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
|
|
48
48
|
- **Fuzzy Matching** - Find players even with spelling variations or nicknames
|
|
49
49
|
- **Live Transfer Trends** - Track the most transferred in/out players for current gameweek
|
|
50
50
|
- **Manager Insights** - Analyze squads, transfers, and chip usage (supports 2025/26 half-season system)
|
|
51
51
|
- **Fixture Analysis** - Assess team fixtures and plan transfers around favorable runs
|
|
52
52
|
|
|
53
|
-
##
|
|
53
|
+
## Quick Start
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
### Option 1: uvx (Recommended)
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
The fastest way to get started - no installation required:
|
|
58
58
|
|
|
59
59
|
```json
|
|
60
60
|
{
|
|
61
61
|
"mcpServers": {
|
|
62
62
|
"fpl": {
|
|
63
|
-
"command": "
|
|
64
|
-
"args": ["
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
"command": "uvx",
|
|
64
|
+
"args": ["fpl-mcp-server"],
|
|
65
|
+
"type": "stdio"
|
|
66
|
+
}
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
### Option 2: Docker
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
Use the official Docker image from GitHub Container Registry:
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"mcpServers": {
|
|
78
|
+
"fpl": {
|
|
79
|
+
"command": "docker",
|
|
80
|
+
"args": [
|
|
81
|
+
"run",
|
|
82
|
+
"--rm",
|
|
83
|
+
"-i",
|
|
84
|
+
"ghcr.io/nguyenanhducs/fpl-mcp:latest"
|
|
85
|
+
],
|
|
86
|
+
"type": "stdio"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
76
91
|
|
|
77
|
-
|
|
78
|
-
git clone https://github.com/nguyenanhducs/fpl-mcp.git
|
|
79
|
-
cd fpl-mcp
|
|
80
|
-
uv sync
|
|
81
|
-
```
|
|
92
|
+
### Option 3: From Source
|
|
82
93
|
|
|
83
|
-
|
|
94
|
+
For development or local customization:
|
|
84
95
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"args": [
|
|
91
|
-
"--directory",
|
|
92
|
-
"/absolute/path/to/fpl-mcp",
|
|
93
|
-
"run",
|
|
94
|
-
"python",
|
|
95
|
-
"-m",
|
|
96
|
-
"src.main"
|
|
97
|
-
],
|
|
98
|
-
"type": "stdio"
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
```
|
|
96
|
+
```bash
|
|
97
|
+
git clone https://github.com/nguyenanhducs/fpl-mcp.git
|
|
98
|
+
cd fpl-mcp
|
|
99
|
+
uv sync
|
|
100
|
+
```
|
|
103
101
|
|
|
104
|
-
|
|
102
|
+
Then configure:
|
|
105
103
|
|
|
106
|
-
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"mcpServers": {
|
|
107
|
+
"fpl": {
|
|
108
|
+
"command": "uv",
|
|
109
|
+
"args": [
|
|
110
|
+
"--directory",
|
|
111
|
+
"/absolute/path/to/fpl-mcp",
|
|
112
|
+
"run",
|
|
113
|
+
"python",
|
|
114
|
+
"-m",
|
|
115
|
+
"src.main"
|
|
116
|
+
],
|
|
117
|
+
"type": "stdio"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
For detailed installation instructions and more options, see **[Installation Guide](./docs/installation.md)**.
|
|
124
|
+
|
|
125
|
+
## Usage & Documentation
|
|
107
126
|
|
|
108
127
|
Once configured, you can interact with the FPL MCP server through Claude Desktop using natural language.
|
|
109
128
|
|
|
@@ -112,26 +131,16 @@ For detailed guidance, see:
|
|
|
112
131
|
- **[Usage Examples](./docs/usage-examples.md)** - Natural language query examples for player analysis, fixtures, leagues, and strategy
|
|
113
132
|
- **[Tool Selection Guide](./docs/tool-selection-guide.md)** - Choose the right tool for your analysis task
|
|
114
133
|
|
|
115
|
-
##
|
|
134
|
+
## Configuration
|
|
116
135
|
|
|
117
136
|
The server works out-of-the-box with sensible defaults, but you can customize cache durations, timeouts, and logging levels through environment variables.
|
|
118
137
|
|
|
119
138
|
For detailed configuration instructions for both Docker and uv deployments, see **[Configuration Guide](./docs/configuration.md)**.
|
|
120
139
|
|
|
121
|
-
##
|
|
140
|
+
## Data Sources
|
|
122
141
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
**Quick contribution checklist:**
|
|
126
|
-
|
|
127
|
-
- Fork the repository
|
|
128
|
-
- Create a feature branch
|
|
129
|
-
- Write tests for new features
|
|
130
|
-
- Ensure all tests pass
|
|
131
|
-
- Follow PEP 8 style guidelines
|
|
132
|
-
- Use conventional commit messages
|
|
133
|
-
- Submit a pull request
|
|
142
|
+
This server uses the official **Fantasy Premier League API**, see [here](./docs/fpl-api.md) for more details.
|
|
134
143
|
|
|
135
|
-
##
|
|
144
|
+
## Contributing
|
|
136
145
|
|
|
137
|
-
|
|
146
|
+
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
@@ -0,0 +1,35 @@
|
|
|
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,,
|
src/client.py
CHANGED
|
@@ -3,6 +3,13 @@ from typing import TYPE_CHECKING, Any, Optional
|
|
|
3
3
|
|
|
4
4
|
import httpx
|
|
5
5
|
|
|
6
|
+
from .constants import (
|
|
7
|
+
TOP_DEFENDERS_COUNT,
|
|
8
|
+
TOP_FORWARDS_COUNT,
|
|
9
|
+
TOP_GOALKEEPERS_COUNT,
|
|
10
|
+
TOP_MIDFIELDERS_COUNT,
|
|
11
|
+
PlayerPosition,
|
|
12
|
+
)
|
|
6
13
|
from .models import Player
|
|
7
14
|
from .rate_limiter import rate_limiter
|
|
8
15
|
|
|
@@ -197,14 +204,24 @@ class FPLClient:
|
|
|
197
204
|
"""
|
|
198
205
|
if not self._store or not self._store.bootstrap_data:
|
|
199
206
|
logger.warning("Bootstrap data not available for top players")
|
|
200
|
-
return {
|
|
207
|
+
return {
|
|
208
|
+
PlayerPosition.GOALKEEPER.value: [],
|
|
209
|
+
PlayerPosition.DEFENDER.value: [],
|
|
210
|
+
PlayerPosition.MIDFIELDER.value: [],
|
|
211
|
+
PlayerPosition.FORWARD.value: [],
|
|
212
|
+
}
|
|
201
213
|
|
|
202
214
|
data = self._store.bootstrap_data
|
|
203
215
|
teams = {t.id: t.name for t in data.teams}
|
|
204
216
|
types = {t.id: t.singular_name_short for t in data.element_types}
|
|
205
217
|
|
|
206
218
|
# Group players by position
|
|
207
|
-
players_by_position = {
|
|
219
|
+
players_by_position = {
|
|
220
|
+
PlayerPosition.GOALKEEPER.value: [],
|
|
221
|
+
PlayerPosition.DEFENDER.value: [],
|
|
222
|
+
PlayerPosition.MIDFIELDER.value: [],
|
|
223
|
+
PlayerPosition.FORWARD.value: [],
|
|
224
|
+
}
|
|
208
225
|
|
|
209
226
|
for element in data.elements:
|
|
210
227
|
# Only include available players
|
|
@@ -237,26 +254,26 @@ class FPLClient:
|
|
|
237
254
|
|
|
238
255
|
# Sort by points_per_game and take top N
|
|
239
256
|
result = {
|
|
240
|
-
|
|
241
|
-
players_by_position[
|
|
257
|
+
PlayerPosition.GOALKEEPER.value: sorted(
|
|
258
|
+
players_by_position[PlayerPosition.GOALKEEPER.value],
|
|
242
259
|
key=lambda x: x["points_per_game"],
|
|
243
260
|
reverse=True,
|
|
244
|
-
)[:
|
|
245
|
-
|
|
246
|
-
players_by_position[
|
|
261
|
+
)[:TOP_GOALKEEPERS_COUNT],
|
|
262
|
+
PlayerPosition.DEFENDER.value: sorted(
|
|
263
|
+
players_by_position[PlayerPosition.DEFENDER.value],
|
|
247
264
|
key=lambda x: x["points_per_game"],
|
|
248
265
|
reverse=True,
|
|
249
|
-
)[:
|
|
250
|
-
|
|
251
|
-
players_by_position[
|
|
266
|
+
)[:TOP_DEFENDERS_COUNT],
|
|
267
|
+
PlayerPosition.MIDFIELDER.value: sorted(
|
|
268
|
+
players_by_position[PlayerPosition.MIDFIELDER.value],
|
|
252
269
|
key=lambda x: x["points_per_game"],
|
|
253
270
|
reverse=True,
|
|
254
|
-
)[:
|
|
255
|
-
|
|
256
|
-
players_by_position[
|
|
271
|
+
)[:TOP_MIDFIELDERS_COUNT],
|
|
272
|
+
PlayerPosition.FORWARD.value: sorted(
|
|
273
|
+
players_by_position[PlayerPosition.FORWARD.value],
|
|
257
274
|
key=lambda x: x["points_per_game"],
|
|
258
275
|
reverse=True,
|
|
259
|
-
)[:
|
|
276
|
+
)[:TOP_FORWARDS_COUNT],
|
|
260
277
|
}
|
|
261
278
|
|
|
262
279
|
return result
|
src/constants.py
CHANGED
|
@@ -3,75 +3,15 @@ Constants and enums for the FPL MCP Server.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from enum import Enum
|
|
6
|
-
from http import HTTPStatus
|
|
7
6
|
|
|
8
|
-
# =============================================================================
|
|
9
|
-
# HTTP Status Codes
|
|
10
|
-
# =============================================================================
|
|
11
|
-
SUCCESS_STATUS = HTTPStatus.OK
|
|
12
|
-
NOT_FOUND_STATUS = HTTPStatus.NOT_FOUND
|
|
13
|
-
SERVER_ERROR_STATUS = HTTPStatus.INTERNAL_SERVER_ERROR
|
|
14
|
-
RATE_LIMIT_STATUS = HTTPStatus.TOO_MANY_REQUESTS
|
|
15
|
-
|
|
16
|
-
# =============================================================================
|
|
17
7
|
# Fuzzy Matching Constants
|
|
18
|
-
# =============================================================================
|
|
19
8
|
FUZZY_MATCH_THRESHOLD = 0.6 # Minimum similarity for fuzzy matches
|
|
20
9
|
SUBSTRING_MATCH_PENALTY = 0.9 # Score multiplier for substring matches
|
|
21
10
|
FUZZY_MATCH_PENALTY = 0.8 # Score multiplier for fuzzy matches
|
|
22
11
|
PERFECT_MATCH_SCORE = 1.0 # Score for exact matches
|
|
23
12
|
|
|
24
|
-
# =============================================================================
|
|
25
|
-
# Cache TTL (Time To Live) in Seconds
|
|
26
|
-
# =============================================================================
|
|
27
|
-
DEFAULT_BOOTSTRAP_TTL = 14400 # 4 hours
|
|
28
|
-
DEFAULT_FIXTURES_TTL = 14400 # 4 hours
|
|
29
|
-
DEFAULT_PLAYER_SUMMARY_TTL = 300 # 5 minutes
|
|
30
|
-
|
|
31
|
-
# =============================================================================
|
|
32
|
-
# Rate Limiting
|
|
33
|
-
# =============================================================================
|
|
34
|
-
MAX_AUTH_ATTEMPTS = 5 # Maximum login attempts
|
|
35
|
-
AUTH_WINDOW_SECONDS = 300 # Time window for rate limiting (5 minutes)
|
|
36
|
-
|
|
37
|
-
# =============================================================================
|
|
38
|
-
# API Timeouts
|
|
39
|
-
# =============================================================================
|
|
40
|
-
DEFAULT_HTTP_TIMEOUT = 30 # Default timeout for HTTP requests
|
|
41
|
-
BROWSER_TIMEOUT = 15 # Browser automation timeout
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# =============================================================================
|
|
45
|
-
# Difficulty Ratings
|
|
46
|
-
# =============================================================================
|
|
47
|
-
class FixtureDifficulty(Enum):
|
|
48
|
-
"""FPL fixture difficulty ratings"""
|
|
49
|
-
|
|
50
|
-
VERY_EASY = 1
|
|
51
|
-
EASY = 2
|
|
52
|
-
MODERATE = 3
|
|
53
|
-
HARD = 4
|
|
54
|
-
VERY_HARD = 5
|
|
55
13
|
|
|
56
|
-
|
|
57
|
-
# Difficulty emoji mapping
|
|
58
|
-
DIFFICULTY_EMOJI = {
|
|
59
|
-
FixtureDifficulty.VERY_EASY: "🟢",
|
|
60
|
-
FixtureDifficulty.EASY: "🟢",
|
|
61
|
-
FixtureDifficulty.MODERATE: "🟡",
|
|
62
|
-
FixtureDifficulty.HARD: "🟠",
|
|
63
|
-
FixtureDifficulty.VERY_HARD: "🔴",
|
|
64
|
-
1: "🟢",
|
|
65
|
-
2: "🟢",
|
|
66
|
-
3: "🟡",
|
|
67
|
-
4: "🟠",
|
|
68
|
-
5: "🔴",
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
# =============================================================================
|
|
73
14
|
# Player Positions
|
|
74
|
-
# =============================================================================
|
|
75
15
|
class PlayerPosition(Enum):
|
|
76
16
|
"""FPL player positions"""
|
|
77
17
|
|
|
@@ -81,38 +21,14 @@ class PlayerPosition(Enum):
|
|
|
81
21
|
FORWARD = "FWD"
|
|
82
22
|
|
|
83
23
|
|
|
84
|
-
# =============================================================================
|
|
85
24
|
# Top Players Count
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
25
|
+
TOP_GOALKEEPERS_COUNT = 5
|
|
26
|
+
TOP_DEFENDERS_COUNT = 20
|
|
27
|
+
TOP_MIDFIELDERS_COUNT = 20
|
|
28
|
+
TOP_FORWARDS_COUNT = 20
|
|
89
29
|
|
|
90
|
-
# =============================================================================
|
|
91
30
|
# Pagination
|
|
92
|
-
# =============================================================================
|
|
93
|
-
DEFAULT_PAGE_SIZE = 50 # FPL API default league standings page size
|
|
94
|
-
DEFAULT_PAGINATION_LIMIT = 20 # MCP recommended default limit for tool responses
|
|
95
31
|
MAX_PAGINATION_LIMIT = 100 # MCP recommended maximum limit
|
|
96
32
|
|
|
97
|
-
# =============================================================================
|
|
98
33
|
# MCP Response Configuration
|
|
99
|
-
# =============================================================================
|
|
100
34
|
CHARACTER_LIMIT = 25000 # Maximum response size in characters (MCP best practice)
|
|
101
|
-
|
|
102
|
-
# =============================================================================
|
|
103
|
-
# FPL URLs
|
|
104
|
-
# =============================================================================
|
|
105
|
-
FPL_BASE_URL = "https://fantasy.premierleague.com"
|
|
106
|
-
FPL_API_BASE = f"{FPL_BASE_URL}/api"
|
|
107
|
-
|
|
108
|
-
# =============================================================================
|
|
109
|
-
# Logging
|
|
110
|
-
# =============================================================================
|
|
111
|
-
DEFAULT_LOG_LEVEL = "INFO"
|
|
112
|
-
WEB_SERVER_LOG_LEVEL = "critical" # Quiet web server logs
|
|
113
|
-
|
|
114
|
-
# =============================================================================
|
|
115
|
-
# Server Configuration
|
|
116
|
-
# =============================================================================
|
|
117
|
-
DEFAULT_WEB_SERVER_HOST = "0.0.0.0"
|
|
118
|
-
DEFAULT_WEB_SERVER_PORT = 8000
|
src/models.py
CHANGED
|
@@ -161,9 +161,6 @@ class FixtureData(BaseModel):
|
|
|
161
161
|
model_config = ConfigDict(extra="allow")
|
|
162
162
|
|
|
163
163
|
|
|
164
|
-
# Models for element-summary endpoint (player details)
|
|
165
|
-
|
|
166
|
-
|
|
167
164
|
class PlayerFixture(BaseModel):
|
|
168
165
|
"""Fixture information for a player"""
|
|
169
166
|
|
|
@@ -269,9 +266,6 @@ class ElementSummary(BaseModel):
|
|
|
269
266
|
history_past: list[PlayerHistoryPast]
|
|
270
267
|
|
|
271
268
|
|
|
272
|
-
# Models for entry endpoint (FPL manager/team info)
|
|
273
|
-
|
|
274
|
-
|
|
275
269
|
class LeaguePhase(BaseModel):
|
|
276
270
|
"""Phase information within a league"""
|
|
277
271
|
|
|
@@ -370,9 +364,6 @@ class ManagerEntry(BaseModel):
|
|
|
370
364
|
model_config = ConfigDict(extra="allow")
|
|
371
365
|
|
|
372
366
|
|
|
373
|
-
# Models for league standings endpoint
|
|
374
|
-
|
|
375
|
-
|
|
376
367
|
class LeagueStandingEntry(BaseModel):
|
|
377
368
|
"""Individual entry in league standings"""
|
|
378
369
|
|
|
@@ -408,9 +399,6 @@ class LeagueStandingsResponse(BaseModel):
|
|
|
408
399
|
model_config = ConfigDict(extra="allow")
|
|
409
400
|
|
|
410
401
|
|
|
411
|
-
# Models for manager gameweek picks endpoint
|
|
412
|
-
|
|
413
|
-
|
|
414
402
|
class AutomaticSub(BaseModel):
|
|
415
403
|
"""Automatic substitution made during a gameweek"""
|
|
416
404
|
|
|
@@ -432,18 +420,15 @@ class PickElement(BaseModel):
|
|
|
432
420
|
model_config = ConfigDict(extra="allow")
|
|
433
421
|
|
|
434
422
|
|
|
435
|
-
# Models for /me endpoint (current user info)
|
|
436
|
-
|
|
437
|
-
|
|
438
423
|
class UserPlayer(BaseModel):
|
|
439
424
|
"""Current user's player information from /me endpoint"""
|
|
440
425
|
|
|
441
426
|
first_name: str
|
|
442
427
|
last_name: str
|
|
443
428
|
email: str
|
|
444
|
-
entry: int
|
|
429
|
+
entry: int
|
|
445
430
|
region: int
|
|
446
|
-
id: int
|
|
431
|
+
id: int
|
|
447
432
|
|
|
448
433
|
model_config = ConfigDict(extra="allow")
|
|
449
434
|
|
|
@@ -491,13 +476,13 @@ class ChipData(BaseModel):
|
|
|
491
476
|
"""Chip information from my-team endpoint"""
|
|
492
477
|
|
|
493
478
|
id: int
|
|
494
|
-
status_for_entry: str
|
|
495
|
-
played_by_entry: list[int]
|
|
496
|
-
name: str
|
|
497
|
-
number: int
|
|
498
|
-
start_event: int
|
|
499
|
-
stop_event: int
|
|
500
|
-
chip_type: str
|
|
479
|
+
status_for_entry: str
|
|
480
|
+
played_by_entry: list[int]
|
|
481
|
+
name: str
|
|
482
|
+
number: int
|
|
483
|
+
start_event: int
|
|
484
|
+
stop_event: int
|
|
485
|
+
chip_type: str
|
|
501
486
|
is_pending: bool
|
|
502
487
|
|
|
503
488
|
model_config = ConfigDict(extra="allow")
|
|
@@ -506,12 +491,12 @@ class ChipData(BaseModel):
|
|
|
506
491
|
class TransfersData(BaseModel):
|
|
507
492
|
"""Transfer information from my-team endpoint"""
|
|
508
493
|
|
|
509
|
-
cost: int
|
|
510
|
-
status: str
|
|
511
|
-
limit: int
|
|
512
|
-
made: int
|
|
513
|
-
bank: int
|
|
514
|
-
value: int
|
|
494
|
+
cost: int
|
|
495
|
+
status: str
|
|
496
|
+
limit: int
|
|
497
|
+
made: int
|
|
498
|
+
bank: int
|
|
499
|
+
value: int
|
|
515
500
|
|
|
516
501
|
model_config = ConfigDict(extra="allow")
|
|
517
502
|
|
src/prompts/__init__.py
CHANGED
|
@@ -6,11 +6,13 @@ from ..tools import mcp
|
|
|
6
6
|
|
|
7
7
|
# Import all prompt modules (this registers prompts with mcp) # noqa: E402
|
|
8
8
|
from . import (
|
|
9
|
+
captain_recommendation, # noqa: F401
|
|
9
10
|
chips, # noqa: F401
|
|
10
11
|
league_analysis, # noqa: F401
|
|
11
12
|
player_analysis, # noqa: F401
|
|
12
13
|
squad_analysis, # noqa: F401
|
|
13
14
|
team_analysis, # noqa: F401
|
|
15
|
+
team_selection, # noqa: F401
|
|
14
16
|
transfers, # noqa: F401
|
|
15
17
|
)
|
|
16
18
|
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FPL MCP Prompts - Captain Recommendation.
|
|
3
|
+
|
|
4
|
+
Prompts guide the LLM in selecting optimal captain choices from the manager's squad
|
|
5
|
+
using form, fixtures, xGI metrics, and opponent defensive strength.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from ..tools import mcp
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@mcp.prompt()
|
|
12
|
+
def recommend_captain(team_id: int, gameweek: int | None = None, response_format: str = "markdown") -> str:
|
|
13
|
+
"""
|
|
14
|
+
Recommend optimal captain choices using xGI-based metrics and fixture analysis.
|
|
15
|
+
|
|
16
|
+
This prompt guides the LLM to analyze squad players and recommend the top 3
|
|
17
|
+
captain options based on form, fixtures, expected goals, and opponent strength.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
team_id: Manager's FPL team ID
|
|
21
|
+
gameweek: Target gameweek (defaults to current/next if None)
|
|
22
|
+
response_format: Output format - 'markdown' (default) or 'json'
|
|
23
|
+
"""
|
|
24
|
+
gameweek_text = f"gameweek {gameweek}" if gameweek else "the current/upcoming gameweek"
|
|
25
|
+
gameweek_display = f"{gameweek}" if gameweek else "Current"
|
|
26
|
+
|
|
27
|
+
return f"""Analyze captain options for team ID {team_id} in {gameweek_text}.
|
|
28
|
+
Act as an FPL Expert Analyst with 10+ years of experience. We do not play it safe; we play for points.
|
|
29
|
+
|
|
30
|
+
**OBJECTIVE: Identify top 3 captain choices using a weighted Pro-Level scoring model.**
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 🚦 **Workflow & Efficiency**
|
|
35
|
+
|
|
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.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 📊 **Pro-Level Scoring Model (Max 100)**
|
|
47
|
+
|
|
48
|
+
Calculate the **Captain Suitability Score** using this weighted matrix:
|
|
49
|
+
|
|
50
|
+
### **1. Projected Points (Weight: 40%)**
|
|
51
|
+
*The core engine. Can they score specific points this week?*
|
|
52
|
+
* **Metric**: Underlying stats (xG + xA) over last 5 GWs + Historical reliability.
|
|
53
|
+
* **Scoring**:
|
|
54
|
+
* **40pts**: Elite stats (xGI > 0.9/match) OR consistent returns (returned in 4/5 last).
|
|
55
|
+
* **30pts**: Good stats (xGI 0.6-0.8) OR decent form.
|
|
56
|
+
* **20pts**: Average stats but good player class.
|
|
57
|
+
* **10pts**: Poor underlying stats.
|
|
58
|
+
|
|
59
|
+
### **2. Fixture Vulnerability (Weight: 30%)**
|
|
60
|
+
*Target specific defensive weaknesses, not just generic FDR.*
|
|
61
|
+
* **Metric**: Opponent strength & Home/Away advantage.
|
|
62
|
+
* **Scoring**:
|
|
63
|
+
* **30pts**: vs Weak Defense (bottom 5) + Home Game.
|
|
64
|
+
* **20pts**: vs Average Defense (Home) OR Weak Defense (Away).
|
|
65
|
+
* **10pts**: vs Strong Defense (Home).
|
|
66
|
+
* **0pts**: vs Elite Defense (Away).
|
|
67
|
+
|
|
68
|
+
### **3. Nailedness & Minutes (Weight: 20%)**
|
|
69
|
+
*Can they hurt us if they don't start?*
|
|
70
|
+
* **Metric**: Minutes played in last 3 weeks + Status.
|
|
71
|
+
* **Scoring**:
|
|
72
|
+
* **20pts**: Nailed (90 mins every game).
|
|
73
|
+
* **15pts**: Secure starter (70-80 mins).
|
|
74
|
+
* **10pts**: Rotation Risk (Pep Roulette / Early Subs).
|
|
75
|
+
* **5pts**: Returning from injury (Start uncertain).
|
|
76
|
+
* **0pts**: benched/injured (Exclude).
|
|
77
|
+
|
|
78
|
+
### **4. Explosiveness Bonus (Weight: 10%)**
|
|
79
|
+
*Do they have a 20-point ceiling?*
|
|
80
|
+
* **Metric**: Penalties, Set Pieces, Multi-goal history.
|
|
81
|
+
* **Scoring**:
|
|
82
|
+
* **10pts**: on Penalties + Hat-trick history.
|
|
83
|
+
* **5pts**: Goalscorer but no penalties.
|
|
84
|
+
* **0pts**: Defensive Midfielder / Low ceiling.
|
|
85
|
+
|
|
86
|
+
**Total Score = Sum of above factors.**
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## ⚠️ **Critical Rules**
|
|
91
|
+
|
|
92
|
+
1. **Risk Flags (Don't auto-exclude):**
|
|
93
|
+
* If a premium player (Price > £10.0m) has `status != 'a'` (available) or played 0 mins recently, **DO NOT** exclude them automatically.
|
|
94
|
+
* Instead, apply a **"Risk Flag"**: penalty to their *Nailedness* score but keep them in the ranking if their upside is huge.
|
|
95
|
+
* *Example:* Haaland returning from injury might play 60 mins but score 2 goals. He is a valid risky captain.
|
|
96
|
+
|
|
97
|
+
2. **Differentials:**
|
|
98
|
+
* If scores are close (<5pts), favor the player with lower ownership (Differential) if chasing rank, or higher ownership (Shield) if protecting rank. Default to *Points Prediction*.
|
|
99
|
+
|
|
100
|
+
3. **Ambiguity:**
|
|
101
|
+
* If stats are missing for a new signing, judge based on *Club Pedigree* and *Fixture* (Assumed 50% "Projected Points" score).
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 📝 **Output Format:**
|
|
106
|
+
|
|
107
|
+
**Top 3 Captain Recommendations for Gameweek {gameweek_display}**
|
|
108
|
+
|
|
109
|
+
🥇 **1. [Player Name]** (Score: [Score]/100) | Confidence: [High/Medium/Low]
|
|
110
|
+
• **Projected Points**: [High/Med/Low] based on xGI & History
|
|
111
|
+
• **Fixture**: [Opponent] (H/A) - Difficulty: [FDR]/5 [⭐]
|
|
112
|
+
• **Nailedness**: [Secure/Risk] - [Minutes played last 3 GWs]
|
|
113
|
+
• **Explosiveness**: [Penalty Duties? / Haul Potential?]
|
|
114
|
+
|
|
115
|
+
**Why**: [2-3 sentence reasoning. Mention specific matchup weaknesses or player form.]
|
|
116
|
+
**Risk**: [Any rotation risk or injury flag? If none, say "None"]
|
|
117
|
+
|
|
118
|
+
**Confidence**: [Justification, e.g., "Clear data leader, 15pt gap to #2"]
|
|
119
|
+
|
|
120
|
+
🥈 **2. [Player Name]** (Score: .../100)
|
|
121
|
+
...
|
|
122
|
+
|
|
123
|
+
🥉 **3. [Player Name]** (Score: .../100)
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
**Confidence Key:**
|
|
128
|
+
• High: Complete data, clear leader
|
|
129
|
+
• Medium: Close competition
|
|
130
|
+
• Low: Uncertain data / Risk factors
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Use emojis for FDR:**
|
|
134
|
+
- FDR 1: ⭐ (very easy)
|
|
135
|
+
- FDR 2: ⭐⭐
|
|
136
|
+
- FDR 3: ⭐⭐⭐
|
|
137
|
+
- FDR 4: ⭐⭐⭐⭐
|
|
138
|
+
- FDR 5: ⭐⭐⭐⭐⭐ (very hard)
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 🔧 **Execution Plan**
|
|
143
|
+
|
|
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.
|
|
150
|
+
|
|
151
|
+
**Begin Analysis Now.**
|
|
152
|
+
"""
|