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
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FPL MCP Prompts - Team Selection.
|
|
3
|
+
|
|
4
|
+
Prompts guide the LLM in selecting the optimal starting XI and bench ordering
|
|
5
|
+
for a specific gameweek.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from ..tools import mcp
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@mcp.prompt()
|
|
12
|
+
def select_team(team_id: int, gameweek: int | None = None) -> str:
|
|
13
|
+
"""
|
|
14
|
+
Optimize Starting XI and Bench using fixture analysis and player status.
|
|
15
|
+
|
|
16
|
+
This prompt guides the LLM to choose the best starting lineup and bench order
|
|
17
|
+
based on fixture difficulty, player availability, and form.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
team_id: Manager's FPL team ID
|
|
21
|
+
gameweek: Target gameweek (defaults to current/next if None)
|
|
22
|
+
"""
|
|
23
|
+
gameweek_text = f"gameweek {gameweek}" if gameweek else "the upcoming gameweek"
|
|
24
|
+
gameweek_display = f"{gameweek}" if gameweek else "Upcoming"
|
|
25
|
+
|
|
26
|
+
return f"""Optimize the Starting XI and Bench for team ID {team_id} in {gameweek_text}.
|
|
27
|
+
**OBJECTIVE: Select the highest-scoring Starting XI and optimize Bench ordering.**
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 🚦 **Workflow & Logic**
|
|
32
|
+
|
|
33
|
+
1. **Get Squad & Status**: Fetch manager's team and check for injuries/suspensions.
|
|
34
|
+
2. **Analyze Fixtures**: Evaluate opponent strength (Attack vs Defense).
|
|
35
|
+
3. **Select Lineup**: Best 11 players regardless of formation (valid formations only: 3-4-3, 3-5-2, 4-4-2, 4-3-3, 5-3-2, etc.).
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 🧠 **Selection Strategy**
|
|
40
|
+
|
|
41
|
+
### **1. Starting XI Priority (Must Starts)**
|
|
42
|
+
* **Premiums**: Always start (e.g., Salah, Haaland) unless injured.
|
|
43
|
+
* **Form Attackers**: Start players with xGI > 0.5 recently, even with tricky fixtures (Attack beats Defense).
|
|
44
|
+
* **Defenders with Clean Sheet Potential**: Start defenders vs Bottom 5 Attacks.
|
|
45
|
+
* **Attacking Defenders**: Start defenders with high xA (e.g., Trent, Porro) regardless of fixture, unless playing Man City/Arsenal away.
|
|
46
|
+
|
|
47
|
+
### **2. Bench Decisions (The "Dilemma" Area)**
|
|
48
|
+
* **Bench Defenders vs Top 6 Attack**: If you have a decent backup mid/fwd, bench the defender playing a top team.
|
|
49
|
+
* **Bench Rotation Risks**: If a player is a massive rotation risk (e.g., Pep Roulette), they can still start if the ceiling is high, but have a secure #1 bench sub ready.
|
|
50
|
+
* **Bench Injured/Suspended**: Move to slots #2 and #3.
|
|
51
|
+
|
|
52
|
+
### **3. Optimizing Bench Order**
|
|
53
|
+
* **Slot 1**: **Highest Ceiling**. The player who can score 10+ points if they come on (e.g., Explosive Winger vs tough defense > 2pt Defender).
|
|
54
|
+
* **Slot 2**: **Safety**. The 90-min defender who guarantees 1-2 points if Slot 1 doesn't play.
|
|
55
|
+
* **Slot 3**: **Fodder/Red Flags**.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 📝 **Output Format:**
|
|
60
|
+
|
|
61
|
+
**Recommended Lineup for Gameweek {gameweek_display}**
|
|
62
|
+
|
|
63
|
+
*(Formation: [e.g. 3-4-3])*
|
|
64
|
+
|
|
65
|
+
**Defense**
|
|
66
|
+
* **GK**: [Name] (vs [Opponent])
|
|
67
|
+
* *Rationale*: [One line reason, e.g., "Opponent lowest xG in league"]
|
|
68
|
+
* **DEF**: [Name] (vs [Opponent])
|
|
69
|
+
* **DEF**: [Name] (vs [Opponent])
|
|
70
|
+
* ...
|
|
71
|
+
|
|
72
|
+
**Midfield**
|
|
73
|
+
* **MID**: [Name] (vs [Opponent])
|
|
74
|
+
* **MID**: [Name] (vs [Opponent])
|
|
75
|
+
* ...
|
|
76
|
+
|
|
77
|
+
**Forwards**
|
|
78
|
+
* **FWD**: [Name] (vs [Opponent])
|
|
79
|
+
* ...
|
|
80
|
+
|
|
81
|
+
**©️ Captain**: [Name] (Run `recommend_captain` for detailed analysis)
|
|
82
|
+
**⚡ Vice-Captain**: [Name] (Secure starter with highest ceiling)
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
**Bench (Critical Order)**
|
|
87
|
+
1. **[Name]** ([Pos] vs [Opponent])
|
|
88
|
+
* *Why #1?*: [e.g., "High ceiling upside despite tough fixture"]
|
|
89
|
+
2. **[Name]** ([Pos] vs [Opponent])
|
|
90
|
+
3. **[Name]** ([Pos] vs [Opponent])
|
|
91
|
+
4. **GK [Name]** ([Pos] vs [Opponent])
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## ⚠️ **Transfer Alert (Optional)**
|
|
96
|
+
* If the team has **>2 non-playing players** (Red flags/Bench fodder) in the starting XI/Bench 1:
|
|
97
|
+
* **Recommendation**: "Consider a transfer for [Player Name] → [Replacement Tool]"
|
|
98
|
+
|
|
99
|
+
## 🔧 **Tool Usage**
|
|
100
|
+
1. `fpl_get_manager_by_team_id(team_id={team_id})` → Get squad.
|
|
101
|
+
2. `fpl_get_gameweek_fixtures(gameweek={gameweek})` → Get matchups.
|
|
102
|
+
3. `fpl_get_player_summary(player_id=...)` -> Check status if flagged.
|
|
103
|
+
|
|
104
|
+
**Begin Selection Analysis.**
|
|
105
|
+
"""
|
src/resources/bootstrap.py
CHANGED
|
@@ -7,6 +7,7 @@ Bootstrap resources expose static FPL data that rarely changes during a season.
|
|
|
7
7
|
from datetime import UTC, datetime
|
|
8
8
|
|
|
9
9
|
from ..client import FPLClient
|
|
10
|
+
from ..constants import PlayerPosition
|
|
10
11
|
from ..state import store
|
|
11
12
|
from ..tools import mcp
|
|
12
13
|
|
|
@@ -31,7 +32,12 @@ async def get_all_players_resource() -> str:
|
|
|
31
32
|
output = [f"**All FPL Players ({len(players)} total)**\n"]
|
|
32
33
|
|
|
33
34
|
# Group by position
|
|
34
|
-
positions = {
|
|
35
|
+
positions = {
|
|
36
|
+
PlayerPosition.GOALKEEPER.value: [],
|
|
37
|
+
PlayerPosition.DEFENDER.value: [],
|
|
38
|
+
PlayerPosition.MIDFIELDER.value: [],
|
|
39
|
+
PlayerPosition.FORWARD.value: [],
|
|
40
|
+
}
|
|
35
41
|
for p in players:
|
|
36
42
|
if p.position in positions:
|
|
37
43
|
positions[p.position].append(p)
|
src/state.py
CHANGED
|
@@ -4,6 +4,12 @@ import logging
|
|
|
4
4
|
from .cache import cache_manager
|
|
5
5
|
from .client import FPLClient
|
|
6
6
|
from .config import settings
|
|
7
|
+
from .constants import (
|
|
8
|
+
FUZZY_MATCH_PENALTY,
|
|
9
|
+
FUZZY_MATCH_THRESHOLD,
|
|
10
|
+
PERFECT_MATCH_SCORE,
|
|
11
|
+
SUBSTRING_MATCH_PENALTY,
|
|
12
|
+
)
|
|
7
13
|
from .models import BootstrapData, ElementData, EventData, FixtureData
|
|
8
14
|
|
|
9
15
|
logger = logging.getLogger("fpl_state")
|
|
@@ -193,7 +199,7 @@ class SessionStore:
|
|
|
193
199
|
# 1. Exact match
|
|
194
200
|
if normalized_query in self.player_name_map:
|
|
195
201
|
for player_id in self.player_name_map[normalized_query]:
|
|
196
|
-
results[player_id] =
|
|
202
|
+
results[player_id] = PERFECT_MATCH_SCORE
|
|
197
203
|
|
|
198
204
|
# 2. Substring match (contains)
|
|
199
205
|
if not results:
|
|
@@ -205,16 +211,16 @@ class SessionStore:
|
|
|
205
211
|
)
|
|
206
212
|
for player_id in player_ids:
|
|
207
213
|
if player_id not in results or similarity > results[player_id]:
|
|
208
|
-
results[player_id] = similarity *
|
|
214
|
+
results[player_id] = similarity * SUBSTRING_MATCH_PENALTY
|
|
209
215
|
|
|
210
216
|
# 3. Fuzzy matching (if enabled and no good matches yet)
|
|
211
217
|
if fuzzy and (not results or max(results.values()) < 0.7):
|
|
212
218
|
for name_key, player_ids in self.player_name_map.items():
|
|
213
219
|
similarity = SequenceMatcher(None, normalized_query, name_key).ratio()
|
|
214
|
-
if similarity >=
|
|
220
|
+
if similarity >= FUZZY_MATCH_THRESHOLD:
|
|
215
221
|
for player_id in player_ids:
|
|
216
222
|
if player_id not in results or similarity > results[player_id]:
|
|
217
|
-
results[player_id] = similarity *
|
|
223
|
+
results[player_id] = similarity * FUZZY_MATCH_PENALTY
|
|
218
224
|
|
|
219
225
|
# Convert to list of tuples and sort by score
|
|
220
226
|
player_matches = [
|
src/tools/__init__.py
CHANGED
|
@@ -6,8 +6,6 @@ from mcp.server.fastmcp import FastMCP
|
|
|
6
6
|
# Create shared MCP instance following Python naming convention: {service}_mcp
|
|
7
7
|
mcp = FastMCP("fpl_mcp")
|
|
8
8
|
|
|
9
|
-
# Import all tool modules (this registers tools with mcp) # noqa: E402
|
|
10
|
-
# Import resources and prompts (this registers them with mcp)
|
|
11
9
|
from .. import (
|
|
12
10
|
prompts, # noqa: F401
|
|
13
11
|
resources, # noqa: F401
|
src/tools/fixtures.py
CHANGED
|
@@ -11,14 +11,8 @@ from ..utils import (
|
|
|
11
11
|
format_json_response,
|
|
12
12
|
handle_api_error,
|
|
13
13
|
)
|
|
14
|
-
|
|
15
|
-
# Import shared mcp instance
|
|
16
14
|
from . import mcp
|
|
17
15
|
|
|
18
|
-
# =============================================================================
|
|
19
|
-
# Pydantic Input Models
|
|
20
|
-
# =============================================================================
|
|
21
|
-
|
|
22
16
|
|
|
23
17
|
class GetFixturesForGameweekInput(BaseModel):
|
|
24
18
|
"""Input model for getting fixtures for a gameweek."""
|
|
@@ -34,11 +28,6 @@ class GetFixturesForGameweekInput(BaseModel):
|
|
|
34
28
|
)
|
|
35
29
|
|
|
36
30
|
|
|
37
|
-
# =============================================================================
|
|
38
|
-
# Helper Functions
|
|
39
|
-
# =============================================================================
|
|
40
|
-
|
|
41
|
-
|
|
42
31
|
async def _create_client():
|
|
43
32
|
"""Create an unauthenticated FPL client for public API access and ensure data is loaded."""
|
|
44
33
|
client = FPLClient(store=store)
|
|
@@ -47,11 +36,6 @@ async def _create_client():
|
|
|
47
36
|
return client
|
|
48
37
|
|
|
49
38
|
|
|
50
|
-
# =============================================================================
|
|
51
|
-
# MCP Tools
|
|
52
|
-
# =============================================================================
|
|
53
|
-
|
|
54
|
-
|
|
55
39
|
@mcp.tool(
|
|
56
40
|
name="fpl_get_fixtures_for_gameweek",
|
|
57
41
|
annotations={
|
src/tools/gameweeks.py
CHANGED
|
@@ -14,14 +14,8 @@ from ..utils import (
|
|
|
14
14
|
format_json_response,
|
|
15
15
|
handle_api_error,
|
|
16
16
|
)
|
|
17
|
-
|
|
18
|
-
# Import shared mcp instance
|
|
19
17
|
from . import mcp
|
|
20
18
|
|
|
21
|
-
# =============================================================================
|
|
22
|
-
# Pydantic Input Models
|
|
23
|
-
# =============================================================================
|
|
24
|
-
|
|
25
19
|
|
|
26
20
|
class GetCurrentGameweekInput(BaseModel):
|
|
27
21
|
"""Input model for getting current gameweek."""
|
|
@@ -57,11 +51,6 @@ class ListAllGameweeksInput(BaseModel):
|
|
|
57
51
|
)
|
|
58
52
|
|
|
59
53
|
|
|
60
|
-
# =============================================================================
|
|
61
|
-
# Helper Functions
|
|
62
|
-
# =============================================================================
|
|
63
|
-
|
|
64
|
-
|
|
65
54
|
async def _create_client():
|
|
66
55
|
"""Create an unauthenticated FPL client for public API access and ensure data is loaded."""
|
|
67
56
|
client = FPLClient(store=store)
|
|
@@ -70,11 +59,6 @@ async def _create_client():
|
|
|
70
59
|
return client
|
|
71
60
|
|
|
72
61
|
|
|
73
|
-
# =============================================================================
|
|
74
|
-
# MCP Tools
|
|
75
|
-
# =============================================================================
|
|
76
|
-
|
|
77
|
-
|
|
78
62
|
@mcp.tool(
|
|
79
63
|
name="fpl_get_current_gameweek",
|
|
80
64
|
annotations={
|
src/tools/leagues.py
CHANGED
|
@@ -12,14 +12,8 @@ from ..utils import (
|
|
|
12
12
|
format_json_response,
|
|
13
13
|
handle_api_error,
|
|
14
14
|
)
|
|
15
|
-
|
|
16
|
-
# Import shared mcp instance
|
|
17
15
|
from . import mcp
|
|
18
16
|
|
|
19
|
-
# =============================================================================
|
|
20
|
-
# Pydantic Input Models
|
|
21
|
-
# =============================================================================
|
|
22
|
-
|
|
23
17
|
|
|
24
18
|
class GetLeagueStandingsInput(BaseModel):
|
|
25
19
|
"""Input model for getting league standings."""
|
|
@@ -99,9 +93,26 @@ class GetManagerSquadInput(BaseModel):
|
|
|
99
93
|
)
|
|
100
94
|
|
|
101
95
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
96
|
+
class GetManagerByTeamIdInput(BaseModel):
|
|
97
|
+
"""Input model for getting manager profile by team ID."""
|
|
98
|
+
|
|
99
|
+
model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True)
|
|
100
|
+
|
|
101
|
+
team_id: int = Field(
|
|
102
|
+
...,
|
|
103
|
+
description="Manager's team ID (entry ID)",
|
|
104
|
+
ge=1,
|
|
105
|
+
)
|
|
106
|
+
gameweek: int | None = Field(
|
|
107
|
+
default=None,
|
|
108
|
+
description="Gameweek number (1-38). If not provided, uses current gameweek",
|
|
109
|
+
ge=1,
|
|
110
|
+
le=38,
|
|
111
|
+
)
|
|
112
|
+
response_format: ResponseFormat = Field(
|
|
113
|
+
default=ResponseFormat.MARKDOWN,
|
|
114
|
+
description="Output format: 'markdown' for human-readable or 'json' for machine-readable",
|
|
115
|
+
)
|
|
105
116
|
|
|
106
117
|
|
|
107
118
|
async def _create_client():
|
|
@@ -112,11 +123,6 @@ async def _create_client():
|
|
|
112
123
|
return client
|
|
113
124
|
|
|
114
125
|
|
|
115
|
-
# =============================================================================
|
|
116
|
-
# MCP Tools
|
|
117
|
-
# =============================================================================
|
|
118
|
-
|
|
119
|
-
|
|
120
126
|
@mcp.tool(
|
|
121
127
|
name="fpl_get_league_standings",
|
|
122
128
|
annotations={
|
|
@@ -588,3 +594,161 @@ async def fpl_get_manager_squad(params: GetManagerSquadInput) -> str:
|
|
|
588
594
|
|
|
589
595
|
except Exception as e:
|
|
590
596
|
return handle_api_error(e)
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
@mcp.tool(
|
|
600
|
+
name="fpl_get_manager_by_team_id",
|
|
601
|
+
annotations={
|
|
602
|
+
"title": "Get Manager Profile by Team ID",
|
|
603
|
+
"readOnlyHint": True,
|
|
604
|
+
"destructiveHint": False,
|
|
605
|
+
"idempotentHint": True,
|
|
606
|
+
"openWorldHint": True,
|
|
607
|
+
},
|
|
608
|
+
)
|
|
609
|
+
async def fpl_get_manager_by_team_id(params: GetManagerByTeamIdInput) -> str:
|
|
610
|
+
"""
|
|
611
|
+
Get manager profile and squad information using team ID directly.
|
|
612
|
+
|
|
613
|
+
This tool provides the same functionality as fpl_get_manager_squad but with
|
|
614
|
+
a name that better reflects its purpose - getting manager information without
|
|
615
|
+
requiring league context. Shows the 15 players picked, captain/vice-captain
|
|
616
|
+
choices, formation, points scored, transfers made, and automatic substitutions.
|
|
617
|
+
|
|
618
|
+
Args:
|
|
619
|
+
params (GetManagerByTeamIdInput): Validated input parameters containing:
|
|
620
|
+
- team_id (int): Manager's team ID (entry ID)
|
|
621
|
+
- gameweek (int | None): Gameweek number (1-38), defaults to current GW
|
|
622
|
+
- response_format (ResponseFormat): 'markdown' or 'json' (default: markdown)
|
|
623
|
+
|
|
624
|
+
Returns:
|
|
625
|
+
str: Complete manager profile with squad, statistics, and team info
|
|
626
|
+
|
|
627
|
+
Examples:
|
|
628
|
+
- View current squad: team_id=123456
|
|
629
|
+
- View specific gameweek: team_id=123456, gameweek=20
|
|
630
|
+
- Get as JSON: team_id=123456, response_format="json"
|
|
631
|
+
|
|
632
|
+
Error Handling:
|
|
633
|
+
- Returns error if team ID not found (404)
|
|
634
|
+
- Returns error if gameweek not started yet
|
|
635
|
+
- Returns formatted error message if API fails
|
|
636
|
+
"""
|
|
637
|
+
try:
|
|
638
|
+
client = await _create_client()
|
|
639
|
+
|
|
640
|
+
# Fetch manager entry to get team name and player name
|
|
641
|
+
try:
|
|
642
|
+
entry_data = await client.get_manager_entry(params.team_id)
|
|
643
|
+
except Exception:
|
|
644
|
+
return f"Manager with team ID {params.team_id} not found. Verify the team ID is correct."
|
|
645
|
+
|
|
646
|
+
team_name = entry_data.get("name", "Unknown Team")
|
|
647
|
+
player_name = f"{entry_data.get('player_first_name', '')} {entry_data.get('player_last_name', '')}".strip()
|
|
648
|
+
|
|
649
|
+
# Determine which gameweek to use
|
|
650
|
+
gameweek = params.gameweek
|
|
651
|
+
if gameweek is None:
|
|
652
|
+
current_gw = store.get_current_gameweek()
|
|
653
|
+
if not current_gw:
|
|
654
|
+
return "Error: Could not determine current gameweek. Please specify a gameweek number."
|
|
655
|
+
gameweek = current_gw.id
|
|
656
|
+
|
|
657
|
+
# Fetch gameweek picks from API
|
|
658
|
+
picks_data = await client.get_manager_gameweek_picks(params.team_id, gameweek)
|
|
659
|
+
|
|
660
|
+
picks = picks_data.get("picks", [])
|
|
661
|
+
entry_history = picks_data.get("entry_history", {})
|
|
662
|
+
auto_subs = picks_data.get("automatic_subs", [])
|
|
663
|
+
|
|
664
|
+
if not picks:
|
|
665
|
+
return f"No team data found for team ID {params.team_id} in gameweek {gameweek}. Gameweek {gameweek} may not have started yet. Please choose an earlier gameweek or wait until GW{gameweek} begins."
|
|
666
|
+
|
|
667
|
+
# Rehydrate player names
|
|
668
|
+
element_ids = [pick["element"] for pick in picks]
|
|
669
|
+
players_info = store.rehydrate_player_names(element_ids)
|
|
670
|
+
|
|
671
|
+
if params.response_format == ResponseFormat.JSON:
|
|
672
|
+
starting_xi = [p for p in picks if p["position"] <= 11]
|
|
673
|
+
bench = [p for p in picks if p["position"] > 11]
|
|
674
|
+
|
|
675
|
+
result = {
|
|
676
|
+
"team_id": params.team_id,
|
|
677
|
+
"team_name": team_name,
|
|
678
|
+
"player_name": player_name,
|
|
679
|
+
"gameweek": gameweek,
|
|
680
|
+
"stats": {
|
|
681
|
+
"points": entry_history.get("points", 0),
|
|
682
|
+
"total_points": entry_history.get("total_points", 0),
|
|
683
|
+
"overall_rank": entry_history.get("overall_rank"),
|
|
684
|
+
"team_value": entry_history.get("value", 0) / 10,
|
|
685
|
+
"bank": entry_history.get("bank", 0) / 10,
|
|
686
|
+
"transfers": entry_history.get("event_transfers", 0),
|
|
687
|
+
"transfer_cost": entry_history.get("event_transfers_cost", 0),
|
|
688
|
+
"points_on_bench": entry_history.get("points_on_bench", 0),
|
|
689
|
+
},
|
|
690
|
+
"active_chip": picks_data.get("active_chip"),
|
|
691
|
+
"starting_xi": [
|
|
692
|
+
{
|
|
693
|
+
"position": pick["position"],
|
|
694
|
+
"player_name": players_info.get(pick["element"], {}).get(
|
|
695
|
+
"web_name", "Unknown"
|
|
696
|
+
),
|
|
697
|
+
"team": players_info.get(pick["element"], {}).get("team", "UNK"),
|
|
698
|
+
"player_position": players_info.get(pick["element"], {}).get(
|
|
699
|
+
"position", "UNK"
|
|
700
|
+
),
|
|
701
|
+
"price": players_info.get(pick["element"], {}).get("price", 0),
|
|
702
|
+
"is_captain": pick["is_captain"],
|
|
703
|
+
"is_vice_captain": pick["is_vice_captain"],
|
|
704
|
+
"multiplier": pick["multiplier"],
|
|
705
|
+
}
|
|
706
|
+
for pick in starting_xi
|
|
707
|
+
],
|
|
708
|
+
"bench": [
|
|
709
|
+
{
|
|
710
|
+
"position": pick["position"],
|
|
711
|
+
"player_name": players_info.get(pick["element"], {}).get(
|
|
712
|
+
"web_name", "Unknown"
|
|
713
|
+
),
|
|
714
|
+
"team": players_info.get(pick["element"], {}).get("team", "UNK"),
|
|
715
|
+
"player_position": players_info.get(pick["element"], {}).get(
|
|
716
|
+
"position", "UNK"
|
|
717
|
+
),
|
|
718
|
+
"price": players_info.get(pick["element"], {}).get("price", 0),
|
|
719
|
+
}
|
|
720
|
+
for pick in bench
|
|
721
|
+
],
|
|
722
|
+
"automatic_subs": [
|
|
723
|
+
{
|
|
724
|
+
"player_out": store.get_player_name(sub["element_out"]),
|
|
725
|
+
"player_in": store.get_player_name(sub["element_in"]),
|
|
726
|
+
}
|
|
727
|
+
for sub in auto_subs
|
|
728
|
+
],
|
|
729
|
+
}
|
|
730
|
+
return format_json_response(result)
|
|
731
|
+
else:
|
|
732
|
+
result = format_manager_squad(
|
|
733
|
+
team_name=team_name,
|
|
734
|
+
player_name=player_name,
|
|
735
|
+
team_id=params.team_id,
|
|
736
|
+
gameweek=gameweek,
|
|
737
|
+
entry_history=entry_history,
|
|
738
|
+
picks=picks,
|
|
739
|
+
players_info=players_info,
|
|
740
|
+
active_chip=picks_data.get("active_chip"),
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
if auto_subs:
|
|
744
|
+
result += "\n\n**Automatic Substitutions:**"
|
|
745
|
+
for sub in auto_subs:
|
|
746
|
+
player_out = store.get_player_name(sub["element_out"])
|
|
747
|
+
player_in = store.get_player_name(sub["element_in"])
|
|
748
|
+
result += f"\n├─ {player_out} → {player_in}"
|
|
749
|
+
|
|
750
|
+
truncated, _ = check_and_truncate(result, CHARACTER_LIMIT)
|
|
751
|
+
return truncated
|
|
752
|
+
|
|
753
|
+
except Exception as e:
|
|
754
|
+
return handle_api_error(e)
|
src/tools/players.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
4
4
|
|
|
5
5
|
from ..client import FPLClient
|
|
6
|
-
from ..constants import CHARACTER_LIMIT
|
|
6
|
+
from ..constants import CHARACTER_LIMIT, PlayerPosition
|
|
7
7
|
from ..formatting import format_player_details
|
|
8
8
|
from ..state import store
|
|
9
9
|
from ..utils import (
|
|
@@ -15,14 +15,8 @@ from ..utils import (
|
|
|
15
15
|
format_status_indicator,
|
|
16
16
|
handle_api_error,
|
|
17
17
|
)
|
|
18
|
-
|
|
19
|
-
# Import shared mcp instance
|
|
20
18
|
from . import mcp
|
|
21
19
|
|
|
22
|
-
# =============================================================================
|
|
23
|
-
# Pydantic Input Models
|
|
24
|
-
# =============================================================================
|
|
25
|
-
|
|
26
20
|
|
|
27
21
|
class SearchPlayersInput(BaseModel):
|
|
28
22
|
"""Input model for searching players by name."""
|
|
@@ -126,11 +120,6 @@ class GetTopPlayersByMetricInput(BaseModel):
|
|
|
126
120
|
)
|
|
127
121
|
|
|
128
122
|
|
|
129
|
-
# =============================================================================
|
|
130
|
-
# Helper Functions
|
|
131
|
-
# =============================================================================
|
|
132
|
-
|
|
133
|
-
|
|
134
123
|
async def _create_client():
|
|
135
124
|
"""Create an unauthenticated FPL client for public API access and ensure data is loaded."""
|
|
136
125
|
client = FPLClient(store=store)
|
|
@@ -280,11 +269,6 @@ async def _aggregate_player_stats_from_fixtures(client: FPLClient, num_gameweeks
|
|
|
280
269
|
}
|
|
281
270
|
|
|
282
271
|
|
|
283
|
-
# =============================================================================
|
|
284
|
-
# MCP Tools
|
|
285
|
-
# =============================================================================
|
|
286
|
-
|
|
287
|
-
|
|
288
272
|
@mcp.tool(
|
|
289
273
|
name="fpl_search_players",
|
|
290
274
|
annotations={
|
|
@@ -440,7 +424,12 @@ async def fpl_search_players_by_team(params: SearchPlayersByTeamInput) -> str:
|
|
|
440
424
|
return f"No players found for {team.name}. This may be a data issue."
|
|
441
425
|
|
|
442
426
|
# Sort by position and price
|
|
443
|
-
position_order = {
|
|
427
|
+
position_order = {
|
|
428
|
+
PlayerPosition.GOALKEEPER.value: 1,
|
|
429
|
+
PlayerPosition.DEFENDER.value: 2,
|
|
430
|
+
PlayerPosition.MIDFIELDER.value: 3,
|
|
431
|
+
PlayerPosition.FORWARD.value: 4,
|
|
432
|
+
}
|
|
444
433
|
players_sorted = sorted(
|
|
445
434
|
players,
|
|
446
435
|
key=lambda p: (position_order.get(p.position or "ZZZ", 5), -p.now_cost),
|
src/tools/teams.py
CHANGED
|
@@ -12,14 +12,8 @@ from ..utils import (
|
|
|
12
12
|
format_json_response,
|
|
13
13
|
handle_api_error,
|
|
14
14
|
)
|
|
15
|
-
|
|
16
|
-
# Import shared mcp instance
|
|
17
15
|
from . import mcp
|
|
18
16
|
|
|
19
|
-
# =============================================================================
|
|
20
|
-
# Pydantic Input Models
|
|
21
|
-
# =============================================================================
|
|
22
|
-
|
|
23
17
|
|
|
24
18
|
class GetTeamInfoInput(BaseModel):
|
|
25
19
|
"""Input model for getting team information."""
|
|
@@ -69,11 +63,6 @@ class AnalyzeTeamFixturesInput(BaseModel):
|
|
|
69
63
|
)
|
|
70
64
|
|
|
71
65
|
|
|
72
|
-
# =============================================================================
|
|
73
|
-
# Helper Functions
|
|
74
|
-
# =============================================================================
|
|
75
|
-
|
|
76
|
-
|
|
77
66
|
async def _create_client():
|
|
78
67
|
"""Create an unauthenticated FPL client for public API access and ensure data is loaded."""
|
|
79
68
|
client = FPLClient(store=store)
|
|
@@ -82,11 +71,6 @@ async def _create_client():
|
|
|
82
71
|
return client
|
|
83
72
|
|
|
84
73
|
|
|
85
|
-
# =============================================================================
|
|
86
|
-
# MCP Tools
|
|
87
|
-
# =============================================================================
|
|
88
|
-
|
|
89
|
-
|
|
90
74
|
@mcp.tool(
|
|
91
75
|
name="fpl_get_team_info",
|
|
92
76
|
annotations={
|
src/tools/transfers.py
CHANGED
|
@@ -12,14 +12,8 @@ from ..utils import (
|
|
|
12
12
|
format_player_price,
|
|
13
13
|
handle_api_error,
|
|
14
14
|
)
|
|
15
|
-
|
|
16
|
-
# Import shared mcp instance
|
|
17
15
|
from . import mcp
|
|
18
16
|
|
|
19
|
-
# =============================================================================
|
|
20
|
-
# Pydantic Input Models
|
|
21
|
-
# =============================================================================
|
|
22
|
-
|
|
23
17
|
|
|
24
18
|
class GetPlayerTransfersByGameweekInput(BaseModel):
|
|
25
19
|
"""Input model for getting player transfer statistics."""
|
|
@@ -56,11 +50,6 @@ class GetManagerTransfersByGameweekInput(BaseModel):
|
|
|
56
50
|
gameweek: int = Field(..., description="Gameweek number (1-38)", ge=1, le=38)
|
|
57
51
|
|
|
58
52
|
|
|
59
|
-
# =============================================================================
|
|
60
|
-
# Helper Functions
|
|
61
|
-
# =============================================================================
|
|
62
|
-
|
|
63
|
-
|
|
64
53
|
async def _create_client():
|
|
65
54
|
"""Create an unauthenticated FPL client for public API access and ensure data is loaded."""
|
|
66
55
|
client = FPLClient(store=store)
|
|
@@ -69,11 +58,6 @@ async def _create_client():
|
|
|
69
58
|
return client
|
|
70
59
|
|
|
71
60
|
|
|
72
|
-
# =============================================================================
|
|
73
|
-
# MCP Tools
|
|
74
|
-
# =============================================================================
|
|
75
|
-
|
|
76
|
-
|
|
77
61
|
@mcp.tool(
|
|
78
62
|
name="fpl_get_player_transfers_by_gameweek",
|
|
79
63
|
annotations={
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
src/cache.py,sha256=SeJAmddaY9507Ac5YRnbBBXGOQw_OwpIefB-kn11lDI,4604
|
|
2
|
-
src/client.py,sha256=9c7jViZx-YavjDeNNeBt43cAqxVW_4NK08ztUbhYvZA,9737
|
|
3
|
-
src/config.py,sha256=hfjW-W0gdH0PxmC6gEg-o9SqraajJ6gNy1SIlIOG-F4,845
|
|
4
|
-
src/constants.py,sha256=kzcVmX__3miaHvs976H_zF2uBG9l-O3EzsLmwSsLJRE,4466
|
|
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=0W6tZ6ZXxJTZrdLda3QGDQ-53XKeJ37GGkekR2w3E7Q,11725
|
|
9
|
-
src/rate_limiter.py,sha256=GLk3ZRFFvEZxkZAQd-pZ7UxQdrAAUVch3pxe_aMU-J8,3450
|
|
10
|
-
src/state.py,sha256=aZwZw9nuI3Ipf2MYVI_IXaLNGdbfUbst5uUZtyLLTLA,17412
|
|
11
|
-
src/utils.py,sha256=WhcWQIXpc1vIjU8hyrGDJyKJSlcbVoG938k_3UMDlCM,7340
|
|
12
|
-
src/validators.py,sha256=aU36TUNYWb26fvZH27Xnryrp8gve9DM2phvy7vEnAi8,6891
|
|
13
|
-
src/prompts/__init__.py,sha256=ArMCl0rgPRwWHgrsHau8Uf1zoPD_HbLniSzfzuEEADU,459
|
|
14
|
-
src/prompts/chips.py,sha256=zzv5bqr8HuUAkvXenonrTXVhwNYGMwH9OPSC-c-1Dtg,5524
|
|
15
|
-
src/prompts/league_analysis.py,sha256=23rNhCYkU8hSmd5BesXgNgHLFo_B8qgszmw909MPHkA,8095
|
|
16
|
-
src/prompts/player_analysis.py,sha256=SGyd0UYWMF0lgml9idfc853UHgXXBT_qLVLf-8PFePU,5242
|
|
17
|
-
src/prompts/squad_analysis.py,sha256=7ixTIrvTITvLIE-9ATH744ci_pObWgzx3p5yUqVHmEk,5204
|
|
18
|
-
src/prompts/team_analysis.py,sha256=lZZ2R1xlsclwy4UyiokMg41ziuCKAqxgN_CoT1mOvnY,4104
|
|
19
|
-
src/prompts/transfers.py,sha256=B99xjzJDTRRdwMluANjKxr5DPWB6eg69nZqJ5uyTosA,5448
|
|
20
|
-
src/resources/__init__.py,sha256=i7nlLVSLtiIrLtOnyoMiK3KTFGEnct4LXApB4b6URFM,303
|
|
21
|
-
src/resources/bootstrap.py,sha256=H6s1vubqNm9I3hcc6U5fdQbEPM_TJNweQvlhKVYCc9Y,6773
|
|
22
|
-
src/tools/__init__.py,sha256=mKHfS7-KsOcMOChZ-xfWTNpShJdKTi61ClnDHiToQQw,644
|
|
23
|
-
src/tools/fixtures.py,sha256=C2d06MX7sbVgX25oHixDorkQyJEbD0DvPdpsrytXMS4,6204
|
|
24
|
-
src/tools/gameweeks.py,sha256=qBsMjdQajiNvt6-DZm8YDdBOYi7ZQY46wST1MuPoK8I,15429
|
|
25
|
-
src/tools/leagues.py,sha256=XRF8h6Dt70wFtGPiU8UD6Rgpe2SAdIM9cgnDOh2geT8,23285
|
|
26
|
-
src/tools/players.py,sha256=Jd4FUzU_qvkc6UE8rMYxZ_EM_nBWnSgIUX4MBz8WoQA,30964
|
|
27
|
-
src/tools/teams.py,sha256=kqths5I7K_1rtsf4HyIzXpn9g8i7uMuCWbJ7YNy_tfQ,14753
|
|
28
|
-
src/tools/transfers.py,sha256=Hy8JXjtbzRjUrtX7kg6IaDZ0IqbstISmt2Ben2Di2XE,24591
|
|
29
|
-
fpl_mcp_server-0.1.4.dist-info/METADATA,sha256=LvkIQ3YyfaNcUU9AVyfp97j4cCWtD85FnJ-aVnV7lzg,4787
|
|
30
|
-
fpl_mcp_server-0.1.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
31
|
-
fpl_mcp_server-0.1.4.dist-info/entry_points.txt,sha256=b3R5hBUMTLVnCGl07NfK7kyq9NCKtpn5Q8OsY79pMek,49
|
|
32
|
-
fpl_mcp_server-0.1.4.dist-info/licenses/LICENSE,sha256=HCDOcdX83voRU2Eip214yj6P_tEyjVjCsCW_sixZFPw,1071
|
|
33
|
-
fpl_mcp_server-0.1.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|