tdf-fantasy-mcp 0.0.1
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.
- package/.github/dependabot.yml +17 -0
- package/.github/workflows/ci.yml +26 -0
- package/.github/workflows/publish.yml +29 -0
- package/.nvmrc +1 -0
- package/CLAUDE.md +174 -0
- package/INDEX.md +111 -0
- package/LICENSE +21 -0
- package/README.md +164 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +991 -0
- package/package.json +33 -0
- package/src/index.ts +1159 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: npm
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
day: monday
|
|
8
|
+
open-pull-requests-limit: 5
|
|
9
|
+
groups:
|
|
10
|
+
mcp-sdk:
|
|
11
|
+
patterns:
|
|
12
|
+
- "@modelcontextprotocol/*"
|
|
13
|
+
typescript-tooling:
|
|
14
|
+
patterns:
|
|
15
|
+
- "typescript"
|
|
16
|
+
- "ts-node"
|
|
17
|
+
- "@types/*"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master, main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master, main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version-file: .nvmrc
|
|
18
|
+
cache: npm
|
|
19
|
+
|
|
20
|
+
- run: npm ci
|
|
21
|
+
|
|
22
|
+
- name: Type-check
|
|
23
|
+
run: npx tsc --noEmit
|
|
24
|
+
|
|
25
|
+
- name: Build
|
|
26
|
+
run: npm run build
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
id-token: write
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version-file: .nvmrc
|
|
20
|
+
cache: npm
|
|
21
|
+
registry-url: https://registry.npmjs.org
|
|
22
|
+
|
|
23
|
+
- run: npm ci
|
|
24
|
+
|
|
25
|
+
- name: Build
|
|
26
|
+
run: npm run build
|
|
27
|
+
|
|
28
|
+
- name: Publish
|
|
29
|
+
run: npm publish --access public --provenance
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# TdF Fantasy MCP — Claude Context
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
MCP server for the [Tour de France Fantasy game](https://fantasy.letour.fr) by Tissot.
|
|
6
|
+
Exposes game data (riders, teams, stages, standings, leaderboards) and fantasy management
|
|
7
|
+
actions as MCP tools, and provides 18 strategic prompts that combine fantasy game data
|
|
8
|
+
with FirstCycling historical stats for richer analysis.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## API
|
|
13
|
+
|
|
14
|
+
**Base URL:** `https://fantasybytissot.letour.fr/v1`
|
|
15
|
+
|
|
16
|
+
**Required header on every request:**
|
|
17
|
+
```
|
|
18
|
+
X-Access-Key: 630@16.17@
|
|
19
|
+
```
|
|
20
|
+
Format: `{identity}@{version}@{codeDemo}` — identity `630` is Tour de France, version `16.17`.
|
|
21
|
+
|
|
22
|
+
**Private endpoints additionally require:**
|
|
23
|
+
```
|
|
24
|
+
Authorization: Token {jwt}
|
|
25
|
+
```
|
|
26
|
+
The JWT comes from logging in at fantasy.letour.fr. Set it via the `TDF_AUTH_TOKEN` environment variable. If the variable is present, the server includes it on public endpoints too (no harm done).
|
|
27
|
+
|
|
28
|
+
**Off-season behaviour:** Between editions (outside roughly June–July), the game enters maintenance mode. Most `/private/` endpoints return `{"message": "Jeu en cours de mise à jour"}`. The `/public/config.json` endpoint stays live year-round and is the main signal that the API is reachable.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Build & Run
|
|
33
|
+
|
|
34
|
+
**Node 18+ required** — the server uses native `fetch` (not available in Node 16).
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install
|
|
38
|
+
npm run build # tsc → dist/
|
|
39
|
+
npm start # node dist/index.js
|
|
40
|
+
npm run dev # ts-node src/index.ts (no build step)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Entry point: `dist/index.js`
|
|
44
|
+
Transport: stdio (MCP standard)
|
|
45
|
+
|
|
46
|
+
Key dependency: `@modelcontextprotocol/sdk` (requires Node ≥ 18).
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Claude Desktop Configuration
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"tdf-fantasy": {
|
|
56
|
+
"command": "node",
|
|
57
|
+
"args": ["/path/to/tdf-fantasy-mcp/dist/index.js"],
|
|
58
|
+
"env": {
|
|
59
|
+
"TDF_AUTH_TOKEN": "your_jwt_token_here"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Run `nvm use` in the project directory first (picks up `.nvmrc` → Node 24 LTS), then `node` resolves correctly.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Tools (18 total)
|
|
71
|
+
|
|
72
|
+
### Public — no auth required
|
|
73
|
+
|
|
74
|
+
| Tool | Endpoint | Description |
|
|
75
|
+
|------|----------|-------------|
|
|
76
|
+
| `get_game_config` | `GET /public/config.json` | Full game config: budget, team size, positions, rules |
|
|
77
|
+
| `get_riders` | `GET /public/sportifs` | All riders with names, teams, prices, positions |
|
|
78
|
+
| `get_teams` | `GET /public/clubs` | All professional cycling teams |
|
|
79
|
+
|
|
80
|
+
### Stages — auth required
|
|
81
|
+
|
|
82
|
+
| Tool | Endpoint | Description |
|
|
83
|
+
|------|----------|-------------|
|
|
84
|
+
| `get_current_stage` | `GET /private/journee` | Current stage info and results |
|
|
85
|
+
| `get_stage` | `GET /private/journee/{n}` | Specific stage by number |
|
|
86
|
+
| `get_stage_calendar` | `GET /private/journeecalendrier/{n}` | Stage schedule/calendar |
|
|
87
|
+
|
|
88
|
+
### Standings — auth required
|
|
89
|
+
|
|
90
|
+
| Tool | Endpoint | Description |
|
|
91
|
+
|------|----------|-------------|
|
|
92
|
+
| `get_general_standings` | `GET /private/classementgeneral/{groupId}` | Overall fantasy leaderboard |
|
|
93
|
+
| `get_stage_standings` | `GET /private/classementjournee/{groupId}/{n}` | Per-stage leaderboard |
|
|
94
|
+
| `get_player_progression` | `GET /private/progressionjoueur/{groupId}/{n}` | Points across stages |
|
|
95
|
+
| `get_hall_of_fame` | `GET /private/halloffame` | All-time top performers |
|
|
96
|
+
|
|
97
|
+
### Leagues — auth required
|
|
98
|
+
|
|
99
|
+
| Tool | Endpoint | Description |
|
|
100
|
+
|------|----------|-------------|
|
|
101
|
+
| `get_my_leagues` | `GET /private/mesgroupes` | Leagues the user belongs to |
|
|
102
|
+
| `get_league` | `GET /private/infosgroupe/{groupId}` | Details about a specific league |
|
|
103
|
+
| `search_leagues` | `POST /private/groupessearch` | Search leagues by name or code |
|
|
104
|
+
|
|
105
|
+
### User & Stats — auth required
|
|
106
|
+
|
|
107
|
+
| Tool | Endpoint | Description |
|
|
108
|
+
|------|----------|-------------|
|
|
109
|
+
| `get_rider_stats` | `POST /private/stats` | Rider stats with optional filters |
|
|
110
|
+
| `get_top_transactions` | `GET /private/toptransaction` | Most bought/sold riders |
|
|
111
|
+
| `get_user_profile` | `GET /private/joueur/{id}` | User fantasy profile |
|
|
112
|
+
| `get_rider_positions` | `GET /private/positions` | Position type definitions |
|
|
113
|
+
| `get_notifications` | `GET /private/notification` | User notifications |
|
|
114
|
+
| `get_user_credits` | `GET /private/usercredits` | Credit balance |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Prompts (18 total)
|
|
119
|
+
|
|
120
|
+
All prompts follow a **two-phase pattern**:
|
|
121
|
+
|
|
122
|
+
1. **Fantasy data phase** — fetch game data (riders, prices, stages, team) using the tdf-fantasy tools above
|
|
123
|
+
2. **FirstCycling enrichment phase** — look up relevant riders on the `firstcycling-mcp` server for historical palmares, terrain-specific performance, and recent form before making any recommendation
|
|
124
|
+
|
|
125
|
+
The user is expected to have `firstcycling-mcp` installed alongside this server. Prompts reference "FirstCycling tools" generically so Claude resolves them at runtime.
|
|
126
|
+
|
|
127
|
+
### Team Building
|
|
128
|
+
|
|
129
|
+
| Prompt | Arguments | Description |
|
|
130
|
+
|--------|-----------|-------------|
|
|
131
|
+
| `strongest_team` | `budget` (req), `strategy` (req: gc/climber/sprinter/puncher/breakaway) | Strongest 8-rider team; validates picks against FirstCycling palmares |
|
|
132
|
+
| `best_value_picks` | `max_price` (req), `specialty` (opt) | Best value under price cap; enriches with FirstCycling form/terrain fit |
|
|
133
|
+
| `design_specialty_team` | `specialty` (req), `budget` (req) | Full team around one specialty; FirstCycling validates track record |
|
|
134
|
+
| `go_all_in_breakaways` | `budget` (req) | Aggressive breakaway stack; FirstCycling verifies actual breakaway history |
|
|
135
|
+
|
|
136
|
+
### Exchange Planning
|
|
137
|
+
|
|
138
|
+
| Prompt | Arguments | Description |
|
|
139
|
+
|--------|-----------|-------------|
|
|
140
|
+
| `plan_transfers` | `stages_ahead` (req), `exchanges_remaining` (req) | Optimal transfer plan; matches stage terrain against FirstCycling terrain specialties |
|
|
141
|
+
| `who_to_drop_before_stage` | `stage_number` (req) | Drop candidates before a stage |
|
|
142
|
+
| `replacement_for_abandoned_rider` | `abandoned_rider_name` (req), `budget` (req) | Replacement after a DNF |
|
|
143
|
+
| `final_week_strategy` | `exchanges_remaining` (req) | Final week transfer plan |
|
|
144
|
+
|
|
145
|
+
### Team Analysis
|
|
146
|
+
|
|
147
|
+
| Prompt | Arguments | Description |
|
|
148
|
+
|--------|-----------|-------------|
|
|
149
|
+
| `analyze_team_form` | none | Team form + FirstCycling recent results to separate bad form from bad luck |
|
|
150
|
+
| `who_is_underperforming` | none | Underperformers; FirstCycling distinguishes fading form vs. due a result |
|
|
151
|
+
| `flag_injury_risks` | none | Injury/fatigue flags; FirstCycling recent results spot pre-Tour injury patterns |
|
|
152
|
+
| `close_gap_on_rival` | `rival_name` (req) | Gap-closing strategy; FirstCycling terrain fit compared across both teams |
|
|
153
|
+
|
|
154
|
+
### Preference-Personalised
|
|
155
|
+
|
|
156
|
+
| Prompt | Arguments | Description |
|
|
157
|
+
|--------|-----------|-------------|
|
|
158
|
+
| `team_around_nationality` | `nationality` (req), `budget` (req) | Nationality-anchored team; FirstCycling ranks candidates by palmares |
|
|
159
|
+
| `team_around_pro_team` | `pro_team_name` (req), `budget` (req) | Pro-team-anchored fantasy team |
|
|
160
|
+
| `find_riders_like_favorite` | `favorite_rider_name` (req) | Similar riders; FirstCycling profile match drives similarity |
|
|
161
|
+
| `specialty_first_team` | `specialty` (req), `budget` (req), `preferred_nationality` (opt) | Specialty-first with optional nationality tie-break |
|
|
162
|
+
| `scout_riders_from_country` | `nationality` (req), `max_price` (req) | Scout a country's riders; FirstCycling palmares is the primary ranking signal |
|
|
163
|
+
| `preferred_riders_value_check` | `rider_names` (req, comma-sep), `budget` (req) | Value check on a personal watchlist |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Rider Position IDs
|
|
168
|
+
|
|
169
|
+
| ID | Label | Description |
|
|
170
|
+
|----|-------|-------------|
|
|
171
|
+
| 43 | Leaders | GC contenders |
|
|
172
|
+
| 44 | Polyvalents | All-rounders |
|
|
173
|
+
| 45 | Grimpeurs | Climbers |
|
|
174
|
+
| 46 | Sprinteurs | Sprinters |
|
package/INDEX.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Codebase Index
|
|
2
|
+
|
|
3
|
+
## File Tree
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
tdf-fantasy-mcp/
|
|
7
|
+
├── src/
|
|
8
|
+
│ └── index.ts # Entire server: API helpers, tools, prompts, MCP wiring
|
|
9
|
+
├── dist/ # Compiled output (git-ignored); generated by `npm run build`
|
|
10
|
+
│ └── index.js # Entry point for production/Claude Desktop
|
|
11
|
+
├── CLAUDE.md # Context for Claude Code sessions — API, tools, prompts reference
|
|
12
|
+
├── INDEX.md # This file
|
|
13
|
+
├── README.md # User-facing docs: setup, tools table, WVA tribute
|
|
14
|
+
├── package.json # Dependencies, build/start/dev scripts, engines: node >=18
|
|
15
|
+
├── package-lock.json # Lockfile
|
|
16
|
+
├── tsconfig.json # target ES2020, commonjs, outDir dist/, rootDir src/
|
|
17
|
+
└── .gitignore # Ignores node_modules/, dist/, .env
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## src/index.ts — Layout
|
|
23
|
+
|
|
24
|
+
The entire implementation lives in one file (~1100 lines). Sections in order:
|
|
25
|
+
|
|
26
|
+
### Imports (lines 1–9)
|
|
27
|
+
```
|
|
28
|
+
@modelcontextprotocol/sdk/server/index.js — Server class
|
|
29
|
+
@modelcontextprotocol/sdk/server/stdio.js — StdioServerTransport
|
|
30
|
+
@modelcontextprotocol/sdk/types.js — schemas, Tool, Prompt types
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### API constants & helpers (lines 11–78)
|
|
34
|
+
| Symbol | Purpose |
|
|
35
|
+
|--------|---------|
|
|
36
|
+
| `API_BASE` | `https://fantasybytissot.letour.fr/v1` |
|
|
37
|
+
| `GAME_IDENTITY` / `GAME_VERSION` | `630` / `16.17` — baked into `X-Access-Key` |
|
|
38
|
+
| `ACCESS_KEY` | `630@16.17@` — sent on every request |
|
|
39
|
+
| `getAuthToken()` | Reads `TDF_AUTH_TOKEN` env var |
|
|
40
|
+
| `buildHeaders(requiresAuth)` | Assembles headers; throws if auth required but token absent |
|
|
41
|
+
| `apiGet(path, requiresAuth)` | Authenticated GET → parsed JSON |
|
|
42
|
+
| `apiPost(path, body, requiresAuth)` | Authenticated POST → parsed JSON |
|
|
43
|
+
|
|
44
|
+
### Tool definitions — `TOOLS` array (lines 80–329)
|
|
45
|
+
18 `Tool` objects with `name`, `description`, and `inputSchema`. Grouped as:
|
|
46
|
+
- Public: `get_game_config`, `get_riders`, `get_teams`
|
|
47
|
+
- Stages: `get_current_stage`, `get_stage`, `get_stage_calendar`
|
|
48
|
+
- Standings: `get_general_standings`, `get_stage_standings`, `get_player_progression`, `get_hall_of_fame`
|
|
49
|
+
- Leagues: `get_my_leagues`, `get_league`, `search_leagues`
|
|
50
|
+
- User/Stats: `get_rider_stats`, `get_top_transactions`, `get_user_profile`, `get_rider_positions`, `get_notifications`, `get_user_credits`
|
|
51
|
+
|
|
52
|
+
### Tool handler — `handleTool()` (lines 331–433)
|
|
53
|
+
`switch` on tool name → calls `apiGet` or `apiPost`. One case per tool.
|
|
54
|
+
|
|
55
|
+
### Prompt definitions — `PROMPTS` array (lines 435–703)
|
|
56
|
+
18 `Prompt` objects with `name`, `description`, and `arguments` array.
|
|
57
|
+
Each argument has `name`, `description`, and `required: boolean`.
|
|
58
|
+
|
|
59
|
+
### Prompt helpers (lines 705–715)
|
|
60
|
+
| Symbol | Purpose |
|
|
61
|
+
|--------|---------|
|
|
62
|
+
| `type Args` | `Record<string, string>` — the raw arguments map from MCP |
|
|
63
|
+
| `req(args, key)` | Reads a required arg; throws with a clear message if missing |
|
|
64
|
+
| `opt(args, key)` | Reads an optional arg; returns `undefined` if absent |
|
|
65
|
+
|
|
66
|
+
### Prompt builder — `buildPromptText()` (lines 717–1023)
|
|
67
|
+
`switch` on prompt name → returns an interpolated string ready to send as a user message.
|
|
68
|
+
Each case calls `req()`/`opt()` for its arguments then builds the prompt text.
|
|
69
|
+
The 11 FirstCycling-enriched prompts explicitly instruct Claude to use FirstCycling tools
|
|
70
|
+
after fetching fantasy data.
|
|
71
|
+
|
|
72
|
+
### MCP server wiring (lines 1025–1096)
|
|
73
|
+
```
|
|
74
|
+
new Server({ name, version }, { capabilities: { tools: {}, prompts: {} } })
|
|
75
|
+
|
|
76
|
+
setRequestHandler(ListToolsRequestSchema) → returns TOOLS
|
|
77
|
+
setRequestHandler(ListPromptsRequestSchema) → returns PROMPTS
|
|
78
|
+
setRequestHandler(GetPromptRequestSchema) → calls buildPromptText(), wraps in message
|
|
79
|
+
setRequestHandler(CallToolRequestSchema) → calls handleTool(), wraps result as text
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Entry point — `main()` (lines 1087–1096)
|
|
83
|
+
Connects a `StdioServerTransport` and logs `"TdF Fantasy MCP server running on stdio"` to stderr.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Gotchas
|
|
88
|
+
|
|
89
|
+
**`get_user_profile` with no `user_id`** calls `/private/joueur/me` — this endpoint may not
|
|
90
|
+
exist on the real API (it was not in the discovered route list). If it 404s, the caller should
|
|
91
|
+
pass an explicit user ID obtained from another endpoint (e.g. league standings).
|
|
92
|
+
|
|
93
|
+
**Off-season responses** — most `/private/` endpoints return a 200 with body
|
|
94
|
+
`{"message": "Jeu en cours de mise à jour"}` rather than a 4xx during maintenance. Tool
|
|
95
|
+
handlers will return this message as valid JSON rather than throwing; callers should check for it.
|
|
96
|
+
|
|
97
|
+
**`get_rider_stats` filter shape** — the POST body is `{ credentials: { positions, clubs, minPrice, maxPrice } }`.
|
|
98
|
+
The API field names are `positions` (not `position_ids`) and `clubs` (not `team_ids`); the
|
|
99
|
+
mapping happens in `handleTool`.
|
|
100
|
+
|
|
101
|
+
**Node 18+ required** — `fetch` is not available in Node 16. The `@modelcontextprotocol/sdk`
|
|
102
|
+
package also declares `engines: { node: ">=18" }`. Use `nvm use 18` or point Claude Desktop's
|
|
103
|
+
`command` at the full Node 18 binary path.
|
|
104
|
+
|
|
105
|
+
**Single-file architecture** — all tools, prompts, and server logic are in `src/index.ts`.
|
|
106
|
+
If the file grows significantly, consider splitting into `src/tools.ts`, `src/prompts.ts`,
|
|
107
|
+
and `src/api.ts` with a thin `src/index.ts` that wires them together.
|
|
108
|
+
|
|
109
|
+
**FirstCycling tool names are not hardcoded** — prompt texts refer to "the FirstCycling tools"
|
|
110
|
+
generically. Claude resolves them at runtime from whatever the `firstcycling-mcp` server
|
|
111
|
+
exposes. This means prompts remain valid even if the firstcycling-mcp tool names change.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ken MacInnis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# TdF Fantasy MCP
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server for the [Tour de France Fantasy game](https://fantasy.letour.fr) by Tissot.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This server exposes the Tour de France Fantasy game API as MCP tools, letting you query riders, teams, stages, standings, and leaderboards from any MCP-compatible client (Claude Desktop, etc.).
|
|
8
|
+
|
|
9
|
+
The underlying API lives at `https://fantasybytissot.letour.fr/v1`.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- **Node.js >= 20** — the server uses native `fetch`, which requires Node 18+ and is stable from Node 20 onward. Check your version with `node --version`; use [nvm](https://github.com/nvm-sh/nvm) to switch if needed.
|
|
14
|
+
- **Recommended companion: [firstcycling-mcp](https://github.com/r-huijts/firstcycling-mcp)** — 11 of the 18 prompts follow a two-phase pattern that enriches fantasy recommendations with FirstCycling historical palmares and terrain data. The tools work without it, but the prompts produce noticeably better output when both servers are connected.
|
|
15
|
+
- **Auth token** — public tools (`get_game_config`, `get_riders`, `get_teams`) work without authentication. All other tools require a valid JWT from the game. See [Authentication](#authentication) below.
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
### Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install
|
|
23
|
+
npm run build
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Authentication
|
|
27
|
+
|
|
28
|
+
Some tools require a valid auth token from the game. To obtain one:
|
|
29
|
+
|
|
30
|
+
1. Open [fantasy.letour.fr](https://fantasy.letour.fr) in your browser
|
|
31
|
+
2. Log in to your account
|
|
32
|
+
3. Open DevTools → Network tab
|
|
33
|
+
4. Make any in-game action and look for requests to `fantasybytissot.letour.fr/v1/private/`
|
|
34
|
+
5. Copy the `Authorization` header value (everything after `Token `)
|
|
35
|
+
|
|
36
|
+
Set it as an environment variable before running:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export TDF_AUTH_TOKEN="your_jwt_token_here"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Tools that don't require auth (`get_game_config`, `get_riders`, `get_teams`) work without a token.
|
|
43
|
+
|
|
44
|
+
### Claude Desktop Configuration
|
|
45
|
+
|
|
46
|
+
Add to your `claude_desktop_config.json`:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"tdf-fantasy": {
|
|
52
|
+
"command": "node",
|
|
53
|
+
"args": ["/path/to/tdf-fantasy-mcp/dist/index.js"],
|
|
54
|
+
"env": {
|
|
55
|
+
"TDF_AUTH_TOKEN": "your_jwt_token_here"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Available Tools
|
|
63
|
+
|
|
64
|
+
### Public (no auth required)
|
|
65
|
+
|
|
66
|
+
| Tool | Description |
|
|
67
|
+
|------|-------------|
|
|
68
|
+
| `get_game_config` | Full game configuration: budget, team size, rider positions, rules |
|
|
69
|
+
| `get_riders` | All riders with names, teams, prices, positions (active season only) |
|
|
70
|
+
| `get_teams` | All professional cycling teams |
|
|
71
|
+
|
|
72
|
+
### Private (auth required)
|
|
73
|
+
|
|
74
|
+
| Tool | Description |
|
|
75
|
+
|------|-------------|
|
|
76
|
+
| `get_current_stage` | Current stage info and results |
|
|
77
|
+
| `get_stage` | Specific stage info by number |
|
|
78
|
+
| `get_stage_calendar` | Stage schedule/calendar |
|
|
79
|
+
| `get_general_standings` | Overall fantasy leaderboard for a group |
|
|
80
|
+
| `get_stage_standings` | Per-stage leaderboard for a group |
|
|
81
|
+
| `get_my_leagues` | Your leagues/groups |
|
|
82
|
+
| `get_league` | Details about a specific league |
|
|
83
|
+
| `search_leagues` | Search for leagues by name or code |
|
|
84
|
+
| `get_rider_stats` | Rider statistics with optional filters |
|
|
85
|
+
| `get_top_transactions` | Most bought/sold riders |
|
|
86
|
+
| `get_user_profile` | User fantasy profile and team |
|
|
87
|
+
| `get_rider_positions` | Position type definitions |
|
|
88
|
+
| `get_hall_of_fame` | All-time top performers |
|
|
89
|
+
| `get_player_progression` | Point progression across stages |
|
|
90
|
+
| `get_notifications` | Your game notifications |
|
|
91
|
+
| `get_user_credits` | Your credit balance |
|
|
92
|
+
|
|
93
|
+
## Rider Positions
|
|
94
|
+
|
|
95
|
+
| ID | Name | Description |
|
|
96
|
+
|----|------|-------------|
|
|
97
|
+
| 43 | Leaders | GC contenders |
|
|
98
|
+
| 44 | Polyvalents | All-rounders |
|
|
99
|
+
| 45 | Grimpeurs | Climbers |
|
|
100
|
+
| 46 | Sprinteurs | Sprinters |
|
|
101
|
+
|
|
102
|
+
## Notes
|
|
103
|
+
|
|
104
|
+
- The API goes into maintenance mode (`{"message": "Jeu en cours de mise à jour"}`) between editions of the Tour de France. Most private endpoints return this message off-season.
|
|
105
|
+
- The game runs annually during the Tour de France (typically July).
|
|
106
|
+
- Budget per team: 120M (standard), 160M (mercato mode)
|
|
107
|
+
- Team size: 8 riders
|
|
108
|
+
|
|
109
|
+
## Development
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm run dev # Run with ts-node (no build step)
|
|
113
|
+
npm run build # Compile to dist/
|
|
114
|
+
npm start # Run compiled output
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Prompts
|
|
118
|
+
|
|
119
|
+
The server exposes 18 MCP prompts that clients like Claude Desktop surface as slash commands or quick actions. Select a prompt, fill in the arguments, and Claude will call the relevant tools automatically.
|
|
120
|
+
|
|
121
|
+
Prompts follow a **two-phase pattern**: first fetch live fantasy data (riders, prices, stage calendar, your team) using the tdf-fantasy tools, then enrich the analysis with [firstcycling-mcp](https://github.com/r-huijts/firstcycling-mcp) for historical palmares, recent form, and terrain-specific performance. Having both servers connected gives the best results.
|
|
122
|
+
|
|
123
|
+
**Example — finding the best value climbers under 14 credits:**
|
|
124
|
+
> Invoke `best_value_picks` with `max_price=14`, `specialty=climber`
|
|
125
|
+
>
|
|
126
|
+
> Claude fetches all affordable riders from the fantasy API, checks the upcoming mountain stage profiles, then looks each candidate up on FirstCycling to verify their climbing palmares and current form before ranking them.
|
|
127
|
+
|
|
128
|
+
### Team Building
|
|
129
|
+
|
|
130
|
+
| Prompt | Arguments | What it does |
|
|
131
|
+
|--------|-----------|--------------|
|
|
132
|
+
| `strongest_team` | `budget` (req), `strategy` (req: gc/climber/sprinter/puncher/breakaway) | Builds the strongest 8-rider team for your budget and strategy; validates picks against FirstCycling palmares |
|
|
133
|
+
| `best_value_picks` | `max_price` (req), `specialty` (opt) | Finds best value-for-money riders under a price cap; enriches with FirstCycling form and terrain fit |
|
|
134
|
+
| `design_specialty_team` | `specialty` (req), `budget` (req) | Designs a full team around one racing specialty; FirstCycling validates each rider's track record |
|
|
135
|
+
| `go_all_in_breakaways` | `budget` (req) | Builds an aggressive breakaway-specialist team; FirstCycling verifies actual breakaway history |
|
|
136
|
+
|
|
137
|
+
### Exchange Planning
|
|
138
|
+
|
|
139
|
+
| Prompt | Arguments | What it does |
|
|
140
|
+
|--------|-----------|--------------|
|
|
141
|
+
| `plan_transfers` | `stages_ahead` (req), `exchanges_remaining` (req) | Plans optimal transfers for the next N stages; matches terrain against FirstCycling specialties before recommending swaps |
|
|
142
|
+
| `who_to_drop_before_stage` | `stage_number` (req) | Identifies drop candidates before a specific stage with replacement suggestions |
|
|
143
|
+
| `replacement_for_abandoned_rider` | `abandoned_rider_name` (req), `budget` (req) | Finds the best replacement after a key rider abandons |
|
|
144
|
+
| `final_week_strategy` | `exchanges_remaining` (req) | Devises an end-of-Tour transfer plan for the mountain stages, TT, and Champs-Élysées |
|
|
145
|
+
|
|
146
|
+
### Team Analysis
|
|
147
|
+
|
|
148
|
+
| Prompt | Arguments | What it does |
|
|
149
|
+
|--------|-----------|--------------|
|
|
150
|
+
| `analyze_team_form` | none | Analyses your team's points trajectory; uses FirstCycling recent results to distinguish bad form from bad luck |
|
|
151
|
+
| `who_is_underperforming` | none | Identifies underperformers relative to price; FirstCycling checks whether they're fading or due a result |
|
|
152
|
+
| `flag_injury_risks` | none | Flags injury and fatigue risks; FirstCycling recent results spot pre-Tour patterns |
|
|
153
|
+
| `close_gap_on_rival` | `rival_name` (req) | Plans a gap-closing strategy; compares both teams' riders via FirstCycling terrain fit for remaining stages |
|
|
154
|
+
|
|
155
|
+
### Preference-Personalised
|
|
156
|
+
|
|
157
|
+
| Prompt | Arguments | What it does |
|
|
158
|
+
|--------|-----------|--------------|
|
|
159
|
+
| `team_around_nationality` | `nationality` (req), `budget` (req) | Builds a team featuring as many riders of a given nationality as possible; ranked by FirstCycling palmares |
|
|
160
|
+
| `team_around_pro_team` | `pro_team_name` (req), `budget` (req) | Anchors a fantasy team around riders from a specific professional team |
|
|
161
|
+
| `find_riders_like_favorite` | `favorite_rider_name` (req) | Finds riders with a similar profile; FirstCycling career match drives the comparison |
|
|
162
|
+
| `specialty_first_team` | `specialty` (req), `budget` (req), `preferred_nationality` (opt) | Builds a specialty-first team with an optional nationality tie-break |
|
|
163
|
+
| `scout_riders_from_country` | `nationality` (req), `max_price` (req) | Scouts a country's riders under a price cap; FirstCycling palmares is the primary ranking signal |
|
|
164
|
+
| `preferred_riders_value_check` | `rider_names` (req, comma-separated), `budget` (req) | Checks whether a personal watchlist fits within budget and at good value |
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|