rivalsdata-api 1.0.0__tar.gz → 1.1.1__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: rivalsdata-api
3
- Version: 1.0.0
3
+ Version: 1.1.1
4
4
  Summary: Python client and MCP server for public Marvel Rivals stats from RivalsData
5
5
  Project-URL: Homepage, https://github.com/GS-Rionnag/rivalsdata-api
6
6
  Project-URL: Repository, https://github.com/GS-Rionnag/rivalsdata-api
@@ -31,6 +31,7 @@ Requires-Dist: ruff>=0.11; extra == 'dev'
31
31
  Provides-Extra: mcp
32
32
  Requires-Dist: mcp-ui-server>=1.0.0; extra == 'mcp'
33
33
  Requires-Dist: mcp<2,>=1.12; extra == 'mcp'
34
+ Requires-Dist: uvicorn>=0.30; extra == 'mcp'
34
35
  Description-Content-Type: text/markdown
35
36
 
36
37
  # rivalsdata-api
@@ -55,7 +56,10 @@ by `python -m camoufox fetch`.
55
56
  ## Quick start
56
57
 
57
58
  ```python
58
- from rivalsdata import RivalsDataClient
59
+ from rivalsdata import RivalsDataClient, hero_id, hero_name
60
+
61
+ print(hero_name(1016)) # Loki
62
+ print(hero_id("Loki")) # 1016
59
63
 
60
64
  with RivalsDataClient() as rd:
61
65
  player = rd.get_player("GS-") # numeric UID works too
@@ -63,6 +67,7 @@ with RivalsDataClient() as rd:
63
67
 
64
68
  # Player profile sections are lazy resource managers.
65
69
  hero_season = player.heroes.fetch(season=20)
70
+ all_hero_seasons = player.heroes.fetch(season="all")
66
71
  map_stats = player.stats.maps(season=20)
67
72
  match_page = player.matches.fetch(season=20)
68
73
 
@@ -91,22 +96,37 @@ python -m pip install 'rivalsdata-api[mcp]'
91
96
  The server exposes read-only tools for player search and profiles, a player's
92
97
  current live match (when they are in one), match history, player stats,
93
98
  leaderboards, heroes, team-ups, public insights, matches, and factions. The
94
- `show_player_dashboard` tool also returns an MCP-UI player report with rank and
95
- competitive record, current match roster split by side, a hero win-rate chart,
96
- and recent match form with K/D/A. It supports local stdio for Claude Desktop
99
+ `show_player_dashboard` tool returns an MCP-UI player card with rank and
100
+ competitive record plus one optional data section per call: current match
101
+ roster, hero win-rate chart, or recent match form with K/D/A. This keeps each
102
+ dashboard pull to the profile plus at most one additional endpoint. It supports
103
+ local stdio for Claude Desktop
97
104
  and Streamable HTTP for remote MCP clients such as ChatGPT. Data comes from
98
105
  RivalsData's undocumented API and may change; profile match history can be
99
106
  private.
100
107
 
108
+ Known `hero_id` and `top_hero_id` fields in MCP results include corresponding
109
+ `hero_name` and `top_hero_name` fields. The `resolve_hero` tool accepts either
110
+ a hero name or numeric ID. The pip package also exports `hero_name(id)` and
111
+ `hero_id(name)`; returned `DataModel` rows provide `.hero_name` and
112
+ `.top_hero_name` conveniences. Those resolved fields are included in mapping
113
+ iteration and `.to_dict()` output to simplify serialization; `.raw` remains the
114
+ untouched source payload.
115
+
101
116
  ### How the MCP UI works
102
117
 
103
118
  `show_player_dashboard` fetches current data, then returns an HTML UI resource
104
- alongside the tool result. MCP-UI labels it with a `ui://` resource URI and
105
- preferred size. A compatible host can render that resource in a sandboxed
106
- panel; a host without UI support can still use the regular MCP tools and their
107
- text/data responses. ChatGPT uses MCP-UI's Apps SDK adapter, while Claude is
108
- listed as supporting MCP Apps directly. The dashboard is a snapshot from the
109
- time the tool runs; ask for it again to refresh.
119
+ alongside the tool result. It advertises the dashboard through
120
+ `_meta.ui.resourceUri`, uses the `text/html;profile=mcp-app` resource MIME type,
121
+ and registers that URI for `resources/read` so the host can actually load the
122
+ app frame. The tool result carries the rendered dashboard as structured content
123
+ for the app frame and an embedded HTML resource for older MCP-UI clients. It
124
+ also includes `openai/outputTemplate` as a ChatGPT compatibility alias. Hosts
125
+ without UI support still receive a text result and can use the regular MCP
126
+ tools. The dashboard is a snapshot from the time the tool runs; ask for it
127
+ again to refresh.
128
+ The `section` argument defaults to `live_match`; use `hero_form` or
129
+ `recent_matches` in separate calls when you need those views.
110
130
 
111
131
  ### Claude Desktop (local)
112
132
 
@@ -133,20 +153,41 @@ after saving the configuration.
133
153
  Run the server on a host reachable over HTTPS:
134
154
 
135
155
  ```console
136
- rivalsdata-mcp --transport streamable-http --host 0.0.0.0 --port 8000
156
+ uvicorn rivalsdata.mcp_server:app --host 0.0.0.0 --port 8000
137
157
  ```
138
158
 
139
159
  The MCP endpoint is `/mcp` (for example, `https://your-host.example/mcp`). Add
140
160
  that endpoint through the client's custom/remote MCP connector settings. The
141
161
  server does not implement authentication; put it behind an authenticated
142
162
  HTTPS gateway before exposing it publicly. For local development, bind to
143
- `127.0.0.1` instead. Use `python -m rivalsdata.mcp_server --help` to see options.
163
+ `127.0.0.1` instead. The `app` is the MCP SDK's Streamable HTTP ASGI
164
+ application; Uvicorn manages its lifespan and session manager.
165
+
166
+ Every implemented response route now has named endpoint models and row models
167
+ with annotations for fields observed in the API inventory. This includes
168
+ `Player`, `Match`, `MatchHistory`, `MatchTeam`, `MatchPlayer`, `Character`,
169
+ `ProficiencyResponse`, `LeaderboardResponse`, `PunishmentsPage`, `XPPage`,
170
+ `Top500Response`, and typed teammate, crosshair, stats, faction, and insight
171
+ records. For example, `rd.matches.get(match_id)` returns a `Match`,
172
+ `player.matches.fetch()` returns a `MatchHistory`, and
173
+ `player.proficiency.fetch()` returns a `ProficiencyResponse`. Nested match
174
+ teams and participants are converted to `MatchTeam` and `MatchPlayer`; embedded
175
+ character records use `Character`. Models support mapping access
176
+ (`player["level"]`) and attribute access (`player.level`). Unknown upstream
177
+ fields are still preserved and available through `.raw`; endpoint schemas
178
+ that have not been observed completely are annotated only for known fields.
144
179
 
145
- `Player` and returned `DataModel` objects support both mapping access and
146
- attribute access (`player["level"]` or `player.level`). Nested dictionaries
147
- and arrays are wrapped recursively; `.raw` returns a shallow copy of a model's
148
- original JSON. For endpoints whose fields evolve, these generic typed wrappers
149
- preserve the complete payload.
180
+ ```python
181
+ with RivalsDataClient() as rd:
182
+ player = rd.get_player(1970288503) # Player
183
+ proficiency = player.proficiency.fetch() # ProficiencyResponse
184
+ account = next(iter(proficiency.accounts.values())) # Proficiency
185
+ hero = account.hero_proficiency_infos["1011"] # HeroProficiency
186
+ print(hero.proficiency_level, hero.proficiency_point)
187
+
188
+ tier_list = rd.heroes.tier_list() # TierListResponse
189
+ print(tier_list.heroes[0].hero_id) # Character
190
+ ```
150
191
 
151
192
  ## Public resources
152
193
 
@@ -20,7 +20,10 @@ by `python -m camoufox fetch`.
20
20
  ## Quick start
21
21
 
22
22
  ```python
23
- from rivalsdata import RivalsDataClient
23
+ from rivalsdata import RivalsDataClient, hero_id, hero_name
24
+
25
+ print(hero_name(1016)) # Loki
26
+ print(hero_id("Loki")) # 1016
24
27
 
25
28
  with RivalsDataClient() as rd:
26
29
  player = rd.get_player("GS-") # numeric UID works too
@@ -28,6 +31,7 @@ with RivalsDataClient() as rd:
28
31
 
29
32
  # Player profile sections are lazy resource managers.
30
33
  hero_season = player.heroes.fetch(season=20)
34
+ all_hero_seasons = player.heroes.fetch(season="all")
31
35
  map_stats = player.stats.maps(season=20)
32
36
  match_page = player.matches.fetch(season=20)
33
37
 
@@ -56,22 +60,37 @@ python -m pip install 'rivalsdata-api[mcp]'
56
60
  The server exposes read-only tools for player search and profiles, a player's
57
61
  current live match (when they are in one), match history, player stats,
58
62
  leaderboards, heroes, team-ups, public insights, matches, and factions. The
59
- `show_player_dashboard` tool also returns an MCP-UI player report with rank and
60
- competitive record, current match roster split by side, a hero win-rate chart,
61
- and recent match form with K/D/A. It supports local stdio for Claude Desktop
63
+ `show_player_dashboard` tool returns an MCP-UI player card with rank and
64
+ competitive record plus one optional data section per call: current match
65
+ roster, hero win-rate chart, or recent match form with K/D/A. This keeps each
66
+ dashboard pull to the profile plus at most one additional endpoint. It supports
67
+ local stdio for Claude Desktop
62
68
  and Streamable HTTP for remote MCP clients such as ChatGPT. Data comes from
63
69
  RivalsData's undocumented API and may change; profile match history can be
64
70
  private.
65
71
 
72
+ Known `hero_id` and `top_hero_id` fields in MCP results include corresponding
73
+ `hero_name` and `top_hero_name` fields. The `resolve_hero` tool accepts either
74
+ a hero name or numeric ID. The pip package also exports `hero_name(id)` and
75
+ `hero_id(name)`; returned `DataModel` rows provide `.hero_name` and
76
+ `.top_hero_name` conveniences. Those resolved fields are included in mapping
77
+ iteration and `.to_dict()` output to simplify serialization; `.raw` remains the
78
+ untouched source payload.
79
+
66
80
  ### How the MCP UI works
67
81
 
68
82
  `show_player_dashboard` fetches current data, then returns an HTML UI resource
69
- alongside the tool result. MCP-UI labels it with a `ui://` resource URI and
70
- preferred size. A compatible host can render that resource in a sandboxed
71
- panel; a host without UI support can still use the regular MCP tools and their
72
- text/data responses. ChatGPT uses MCP-UI's Apps SDK adapter, while Claude is
73
- listed as supporting MCP Apps directly. The dashboard is a snapshot from the
74
- time the tool runs; ask for it again to refresh.
83
+ alongside the tool result. It advertises the dashboard through
84
+ `_meta.ui.resourceUri`, uses the `text/html;profile=mcp-app` resource MIME type,
85
+ and registers that URI for `resources/read` so the host can actually load the
86
+ app frame. The tool result carries the rendered dashboard as structured content
87
+ for the app frame and an embedded HTML resource for older MCP-UI clients. It
88
+ also includes `openai/outputTemplate` as a ChatGPT compatibility alias. Hosts
89
+ without UI support still receive a text result and can use the regular MCP
90
+ tools. The dashboard is a snapshot from the time the tool runs; ask for it
91
+ again to refresh.
92
+ The `section` argument defaults to `live_match`; use `hero_form` or
93
+ `recent_matches` in separate calls when you need those views.
75
94
 
76
95
  ### Claude Desktop (local)
77
96
 
@@ -98,20 +117,41 @@ after saving the configuration.
98
117
  Run the server on a host reachable over HTTPS:
99
118
 
100
119
  ```console
101
- rivalsdata-mcp --transport streamable-http --host 0.0.0.0 --port 8000
120
+ uvicorn rivalsdata.mcp_server:app --host 0.0.0.0 --port 8000
102
121
  ```
103
122
 
104
123
  The MCP endpoint is `/mcp` (for example, `https://your-host.example/mcp`). Add
105
124
  that endpoint through the client's custom/remote MCP connector settings. The
106
125
  server does not implement authentication; put it behind an authenticated
107
126
  HTTPS gateway before exposing it publicly. For local development, bind to
108
- `127.0.0.1` instead. Use `python -m rivalsdata.mcp_server --help` to see options.
127
+ `127.0.0.1` instead. The `app` is the MCP SDK's Streamable HTTP ASGI
128
+ application; Uvicorn manages its lifespan and session manager.
129
+
130
+ Every implemented response route now has named endpoint models and row models
131
+ with annotations for fields observed in the API inventory. This includes
132
+ `Player`, `Match`, `MatchHistory`, `MatchTeam`, `MatchPlayer`, `Character`,
133
+ `ProficiencyResponse`, `LeaderboardResponse`, `PunishmentsPage`, `XPPage`,
134
+ `Top500Response`, and typed teammate, crosshair, stats, faction, and insight
135
+ records. For example, `rd.matches.get(match_id)` returns a `Match`,
136
+ `player.matches.fetch()` returns a `MatchHistory`, and
137
+ `player.proficiency.fetch()` returns a `ProficiencyResponse`. Nested match
138
+ teams and participants are converted to `MatchTeam` and `MatchPlayer`; embedded
139
+ character records use `Character`. Models support mapping access
140
+ (`player["level"]`) and attribute access (`player.level`). Unknown upstream
141
+ fields are still preserved and available through `.raw`; endpoint schemas
142
+ that have not been observed completely are annotated only for known fields.
109
143
 
110
- `Player` and returned `DataModel` objects support both mapping access and
111
- attribute access (`player["level"]` or `player.level`). Nested dictionaries
112
- and arrays are wrapped recursively; `.raw` returns a shallow copy of a model's
113
- original JSON. For endpoints whose fields evolve, these generic typed wrappers
114
- preserve the complete payload.
144
+ ```python
145
+ with RivalsDataClient() as rd:
146
+ player = rd.get_player(1970288503) # Player
147
+ proficiency = player.proficiency.fetch() # ProficiencyResponse
148
+ account = next(iter(proficiency.accounts.values())) # Proficiency
149
+ hero = account.hero_proficiency_infos["1011"] # HeroProficiency
150
+ print(hero.proficiency_level, hero.proficiency_point)
151
+
152
+ tier_list = rd.heroes.tier_list() # TierListResponse
153
+ print(tier_list.heroes[0].hero_id) # Character
154
+ ```
115
155
 
116
156
  ## Public resources
117
157
 
@@ -3,9 +3,11 @@
3
3
  This is an observed inventory of the public web client's API, gathered by
4
4
  reviewing the RivalsData UI and its browser requests on 2026-09-29. The upstream
5
5
  API is undocumented and can change. Field sets below are examples from live
6
- responses, not schemas guaranteed by RivalsData. Response bodies are returned
7
- as attribute-accessible `DataModel` / `StatRecord` objects, preserving unknown
8
- keys.
6
+ responses, not schemas guaranteed by RivalsData. The client converts every
7
+ implemented route to endpoint-specific model and row classes (such as `Match`,
8
+ `Character`, `PlayerSummary`, `ProficiencyResponse`, and `PunishmentsPage`).
9
+ Known fields are annotated; unknown keys remain accessible through `DataModel`
10
+ and `.raw` so upstream additions are not discarded.
9
11
 
10
12
  ## Implemented public read endpoints
11
13
 
@@ -14,31 +16,31 @@ keys.
14
16
  | Search | `POST /players/search` | `{"name": name}` | Search rows; `aid` may contain the numeric UID after the final `_`. |
15
17
  | Player overview | `POST /player` | `{"uid": number}` | Object keys observed: `cached_at`, `claimed`, `faction`, `icon`, `last_seen`, `leaderboard`, `level`, `login_os`, `match_history_is_visible`, `mood`, `name`, `rank_game_season`, `status`, `uid`, `xp`. `rank_game_season` is keyed by game/season ids; competitive rows include `battle_count`, `rank_game_id`, `rank_score`, and `win_count`. |
16
18
  | Player live game | `POST /live` | `{"match_id": status.battle_id, "uid": number}` | Object keys observed: `players` (12 player entries keyed by team slot) and `team_avg_rank` (rank averages keyed by side). Player rows include `ai`, `games`, `icon`, `losses`, `name`, `proficiency`, `rank`, `side`, `team_id`, `top_heroes`, `uid`, and `wins`. The match ID comes from the player's current `/player` response; the endpoint was observed on a profile marked `In game (Competitive)`. |
17
- | Player hero summary | `POST /player/heroes` | `uid`, optional `season` | Array rows: `assists`, `deaths`, `games`, `hero_id`, `kda`, `kills`, `losses`, `rank`, `wins`. |
19
+ | Player hero summary | `POST /player/heroes` | `uid`, optional `season`; wrapper `season="all"` sends season ID `-1` | Array rows: `assists`, `deaths`, `games`, `hero_id`, `kda`, `kills`, `losses`, `rank`, `wins`. The all-seasons selector was verified against the public `GS-` profile; `season=-1` returned 40 hero rows, while `season=0` returned none. |
18
20
  | Player crosshairs | `POST /player/crosshairs` | `uid` | Array rows: `crosshair`, `uses`. |
19
21
  | Player match history | `POST /player/matches/cached` (or `/player/matches`) | `uid`, `cursor`, optional `season`; cached endpoint also accepts `mode`, `hero`, `teammate` | Cached response keys observed: `matches`, `next_cursor`, `source`; match rows include `assists`, `deaths`, `game_mode_id`, `game_play_mode_id`, `hero_id`, `is_mvp`, `is_svp`, `is_win`, `kills`, `map_id`, `match_uid`, `os`, `placement`, `platform`, `rank_level`, `rank_score`, `score_change`, `season`, `team_score`, `timestamp`, `winner_camp`. A sample profile marked its history private; a private profile can return no visible match rows. |
20
22
  | Player teammates | `POST /player/teammates` | `uid`, optional `season`, `mode` | Array rows: `games`, `icon`, `losses`, `name`, `teammate_uid`, `wins`. |
21
- | Player proficiency | `POST /player/proficiency` | `uid` | Object keyed by account id (sample: `11001_{uid}`); inner structure varies. |
22
- | Player hero stats | `POST /player/stats/heroes` | `uid`, optional `season` | Array rows: `competitive`, `hero_id`, `quickplay`, `rank`. |
23
- | Player map stats | `POST /player/stats/maps` | `uid`, optional `season` | Array shape varies; captured from the profile Stats tab. |
24
- | Player ban stats | `POST /player/stats/bans` | `uid`, optional `season` | Array shape varies; captured from the profile Stats tab. |
25
- | Player punishments | `POST /player/punishments` | `uid` | Object keys observed: `chat`, `login`, `rank`. |
23
+ | Player proficiency | `POST /player/proficiency` | `uid` | Object keyed by account id (sample: `11001_{uid}`); each account has `hero_proficiency_infos` keyed by hero id, with `proficiency_level` and `proficiency_point`. |
24
+ | Player hero stats | `POST /player/stats/heroes` | `uid`, optional `season` | Array rows: `competitive`, `hero_id`, `quickplay`, `rank`. Each mode includes games, wins/losses, KDA, MVP/SVP counts, accuracy, and `per_10`/`per_game` combat averages. |
25
+ | Player map stats | `POST /player/stats/maps` | `uid`, optional `season` | Array rows: `map`, `competitive`, `quickplay`; each mode has games, wins, losses, winrate. |
26
+ | Player ban stats | `POST /player/stats/bans` | `uid`, optional `season` | Array rows: `hero_id`, `matches`, `wins`, `losses`, `winrate`. |
27
+ | Player punishments | `POST /player/punishments` | `uid` | Object keys: `chat`, `login`, `rank`; non-null entries include `expire`, `name`, `reason`, `time`, `uid`. |
26
28
  | Player name history | `POST /player/name-history` | `uid` | Array rows: `first_seen`, `name`. |
27
29
  | Global leaderboard | `GET /leaderboards` | `limit`, optional `skip`, `season`, `os` (Python `platform`) | Object keys observed: `count`, `players`, `updated_at`; row keys: `heroes`, `icon`, `losses`, `name`, `os`, `position`, `rank_level`, `rank_score`, `season`, `status`, `uid`, `wins`. |
28
- | Hero tier list | `GET /stats/tierlist` | `platform`, `rank` | Hero rows displayed with tier, win rate, pick rate, ban rate, and games; raw row keys preserved. |
29
- | Hero detail | `GET /stats/heroes/{hero_id}` | Path parameter | Hero aggregate object; full key set varies by hero and season. |
30
- | Hero trend/meta | `GET /stats/meta/{hero_id}` | `range` (30, 90, or 180 days) | Trend/analytics object; raw keys preserved. |
31
- | Hero leaderboard | `GET /stats/leaderboards` | `hero` plus caller-supplied query filters | Used by hero leaderboard pages. Parameter combinations and response fields are not fully verified. |
32
- | Team-up stats | `GET /stats/teamups` | `platform`, `rank`, optional `hero` | Team-up usage and win-rate rows; raw keys preserved. |
30
+ | Hero tier list | `GET /stats/tierlist` | `platform`, `rank` | Object: `last_update`, `heroes`; rows include hero id, picks/bans, total games, winrate, pick rate, ban rate, and score. |
31
+ | Hero detail | `GET /stats/heroes/{hero_id}` | Path parameter | Object: `hero`, `last_update`, `season`; `hero` contains aggregate per-10 stats and pick/ban rates. |
32
+ | Hero trend/meta | `GET /stats/meta/{hero_id}` | `range` as `30d`, `90d`, or `180d` | Object: `hero_id`, `last_update`, `points`, `range`, `window_days`; point rows include timestamp, games, pick/ban rates, and winrate without mirror matches. |
33
+ | Hero leaderboard | `GET /stats/leaderboards` | `hero` plus caller-supplied query filters | Object: `last_update`, `players`; rows include combat averages, placement, score, rank, and wins/losses. |
34
+ | Team-up stats | `GET /stats/teamups` | `platform`, `rank`, optional `hero` | Object: `last_update`, `heroes`, where `heroes` maps hero ids to slot ids and rows with `bond_id`, `games`, `nm_winrate`, `pickrate`, `winrate`. |
33
35
  | Punishments log | `GET /stats/punishments` | `kind`, optional `cursor` | Object: `last_update`, `next`, `results`; sample row keys: `expires_at`, `icon`, `issued_at`, `kind`, `name`, `peak_rank_level`, `peak_rank_score`, `rank`, `reason`, `uid`. |
34
36
  | XP leaderboard | `GET /stats/xp` | Optional `cursor` | Object: `last_update`, `next`, `results`; sample row keys: `icon`, `name`, `rank`, `uid`, `xp`. |
35
37
  | Top 500 finishes | `GET /stats/oaa` | `os` (Python `platform`) | Object: `count`, `last_update`, `os`, `players`; sample row keys: `avg_placement`, `avg_score`, `finishes`, `icon`, `name`, `seasons`, `uid`. |
36
38
  | Hero comm-ban insight | `GET /stats/commbans` | `mode` (`all` or `competitive`) | Object: `heroes`, `last_update`, `mode`, `overall_pct`; hero rows: `ci95`, `hero_id`, `pct`, `qualifying_players`, `vs_avg`, `weighted_banned`, `weighted_players`. |
37
39
  | Hero AFK insight | `GET /stats/leavers` | `mode` (`all` or `competitive`) | Object: `heroes`, `last_update`, `mode`, `overall_pct`; hero rows: `ci95`, `games`, `hero_id`, `leaves`, `leaves_per_player`, `pct`, `players`, `vs_avg`. |
38
- | Faction details | `GET /faction/{faction_id}` | Path parameter | Faction overview, public profile/member list, and results; nested schema varies. |
40
+ | Faction details | `GET /faction/{faction_id}` | Path parameter | Faction `captain`, `description`, `members`, `name`, `region`, `results`, `tag`, `type`; member records contain account id, config/rank, game status, and name. |
39
41
  | Match details | `POST /match` | `{"match_id": "..."}` | Object keys observed: `match_uid`, `replay_id`, `winner_camp`, `duration_seconds`, `map_id`, `game_mode_id`, `game_play_mode_id`, `platform`, `timestamp`, `draft`, `teams`; team player rows include combat stats and per-hero usage. |
40
- | Public profile card | `GET /profiles/{username}` | Username path parameter | Route observed in the profile frontend; complete response schema not captured. |
41
- | Favorites lookup | `POST /favorites` | `{"uids": [uid, ...]}` | Request observed in frontend assets; response schema not captured. |
42
+ | Public profile card | `GET /profiles/{uid}` | Numeric UID path parameter; `Profiles.get` resolves usernames | Object: `leaderboard_social`, `socials`, `uid`, `updated_at`. |
43
+ | Favorites lookup | `POST /favorites` | `{"uids": [numeric_uid, ...]}` | Array of public player summaries with `aid`, `config_server`, `games`, `name`, and `status`. |
42
44
 
43
45
  The client exposes these read resources through `RivalsDataClient` and `Player`;
44
46
  see README examples and method docstrings. `DataModel.win_rate` returns an
@@ -15,7 +15,7 @@ as public, stable methods. Keep requests respectful and conservative.
15
15
  ## Current package
16
16
 
17
17
  - Distribution: `rivalsdata-api`; import: `rivalsdata`.
18
- - Version: `0.2.0` (update metadata deliberately before the next release).
18
+ - Version: `1.1.0`.
19
19
  - Python `>=3.10`, Hatchling build, `src/` layout.
20
20
  - Runtime HTTP dependency: `curl-cffi`; optional browser fallback: Camoufox.
21
21
  - Public entry point: `RivalsDataClient`.
@@ -48,8 +48,9 @@ Lazy subresources include `player.heroes.fetch(...)`, `player.matches.fetch(...)
48
48
  `player.name_history.fetch()`, and `player.stats.heroes/maps/bans(...)`.
49
49
  Client-wide resources include `client.leaderboards`, `client.heroes`,
50
50
  `client.team_ups`, `client.insights`, `client.factions`, and `client.matches`.
51
- `client.profiles` and `client.favorites` have thin read methods; their response
52
- schemas are not yet verified.
51
+ `client.profiles` and `client.favorites` have typed read methods. The profile
52
+ endpoint takes a numeric UID; the wrapper can resolve a username first.
53
+ Favorites requires numeric UIDs and returns player summary rows.
53
54
  Rows offer `.win_rate` and `.winrate` integer-percent access when data supports
54
55
  it; all original data remains in mapping access.
55
56
 
@@ -70,8 +71,8 @@ detail pages. Main public request families are:
70
71
  opened from the public GS- profile. The returned object contains replay id,
71
72
  mode/map/time, draft picks/bans, both teams, players' combat stats, and hero
72
73
  usage.
73
- - `GET /profiles/{username}` and `POST /favorites` are wrapped in generic
74
- models. Their response contracts remain undocumented.
74
+ - `GET /profiles/{uid}` returns profile social metadata and `POST /favorites`
75
+ returns public player summaries. Both were checked through the browser.
75
76
 
76
77
  The hero detail Counters and Synergy tabs showed “Coming Soon” on inspection.
77
78
  The Live Game tab did not expose data for the sampled player. Do not invent
@@ -84,7 +85,8 @@ endpoints can change account state and are intentionally not implemented by
84
85
  this read-only package yet. `client.matches.get` uses the verified `match_id`
85
86
  payload.
86
87
 
87
- The website currently shows Season 10 / season value 20 and OS `1` for PC on
88
+ The hero meta endpoint requires `range=30d`, `90d`, or `180d`; bare integers
89
+ are rejected. The website currently shows Season 10 / season value 20 and OS `1` for PC on
88
90
  the inspected UI. Treat those as site values, not permanent constants.
89
91
 
90
92
  ## Contributor workflow
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "rivalsdata-api"
7
- version = "1.0.0"
7
+ version = "1.1.1"
8
8
  description = "Python client and MCP server for public Marvel Rivals stats from RivalsData"
9
9
  keywords = ["marvel-rivals", "rivalsdata", "game-stats", "api-client", "mcp"]
10
10
  readme = "README.md"
@@ -34,12 +34,9 @@ Changelog = "https://github.com/GS-Rionnag/rivalsdata-api/releases"
34
34
 
35
35
  [project.optional-dependencies]
36
36
  browser = ["camoufox>=0.5.6"]
37
- mcp = ["mcp>=1.12,<2", "mcp-ui-server>=1.0.0"]
37
+ mcp = ["mcp>=1.12,<2", "mcp-ui-server>=1.0.0", "uvicorn>=0.30"]
38
38
  dev = ["build>=1.2", "pytest>=8", "ruff>=0.11"]
39
39
 
40
- [project.scripts]
41
- rivalsdata-mcp = "rivalsdata.mcp_server:main"
42
-
43
40
  [tool.ruff]
44
41
  line-length = 88
45
42
  target-version = "py310"
@@ -0,0 +1,148 @@
1
+ """Unofficial client for public RivalsData player data."""
2
+
3
+ from .client import RivalsDataClient
4
+ from .exceptions import (
5
+ CloudflareError,
6
+ PlayerNotFoundError,
7
+ RivalsDataError,
8
+ RivalsDataHTTPError,
9
+ )
10
+ from .hero_ids import HERO_NAMES, hero_id, hero_name
11
+ from .models import (
12
+ BanRecord,
13
+ Character,
14
+ CombatAverages,
15
+ CommBanHero,
16
+ CommBanInsights,
17
+ CrosshairRecord,
18
+ DataModel,
19
+ DraftEntry,
20
+ Faction,
21
+ FactionConfig,
22
+ FactionGame,
23
+ FactionMember,
24
+ FactionRank,
25
+ FactionResult,
26
+ FactionSummary,
27
+ FavoritePlayer,
28
+ FavoritesResponse,
29
+ HeroDetail,
30
+ HeroInsights,
31
+ HeroLeaderboardPlayer,
32
+ HeroLeaderboardResponse,
33
+ HeroMeta,
34
+ HeroMetaPoint,
35
+ HeroModeStats,
36
+ HeroProficiency,
37
+ HeroStatsRecord,
38
+ LeaderboardPlayer,
39
+ LeaderboardResponse,
40
+ LeaverHero,
41
+ LeaverInsights,
42
+ LiveGame,
43
+ LiveGamePlayer,
44
+ MapModeStats,
45
+ MapRecord,
46
+ Match,
47
+ MatchHistory,
48
+ MatchPlayer,
49
+ MatchTeam,
50
+ NameHistoryRecord,
51
+ Player,
52
+ PlayerPunishments,
53
+ PlayerSearchResult,
54
+ PlayerStatus,
55
+ PlayerSummary,
56
+ Proficiency,
57
+ ProficiencyResponse,
58
+ ProfileCard,
59
+ PunishmentEntry,
60
+ PunishmentRecord,
61
+ PunishmentsPage,
62
+ RankRecord,
63
+ StatRecord,
64
+ Teammate,
65
+ TeamScore,
66
+ TeamUpRecord,
67
+ TeamUpsResponse,
68
+ TierListResponse,
69
+ Top500Player,
70
+ Top500Response,
71
+ Top500Season,
72
+ XPPage,
73
+ XPRecord,
74
+ )
75
+
76
+ __all__ = [
77
+ "HERO_NAMES",
78
+ "BanRecord",
79
+ "Character",
80
+ "CloudflareError",
81
+ "CombatAverages",
82
+ "CommBanHero",
83
+ "CommBanInsights",
84
+ "CrosshairRecord",
85
+ "DataModel",
86
+ "DraftEntry",
87
+ "Faction",
88
+ "FactionConfig",
89
+ "FactionGame",
90
+ "FactionMember",
91
+ "FactionRank",
92
+ "FactionResult",
93
+ "FactionSummary",
94
+ "FavoritePlayer",
95
+ "FavoritesResponse",
96
+ "HeroDetail",
97
+ "HeroInsights",
98
+ "HeroLeaderboardPlayer",
99
+ "HeroLeaderboardResponse",
100
+ "HeroMeta",
101
+ "HeroMetaPoint",
102
+ "HeroModeStats",
103
+ "HeroProficiency",
104
+ "HeroStatsRecord",
105
+ "LeaderboardPlayer",
106
+ "LeaderboardResponse",
107
+ "LeaverHero",
108
+ "LeaverInsights",
109
+ "LiveGame",
110
+ "LiveGamePlayer",
111
+ "MapModeStats",
112
+ "MapRecord",
113
+ "Match",
114
+ "MatchHistory",
115
+ "MatchPlayer",
116
+ "MatchTeam",
117
+ "NameHistoryRecord",
118
+ "Player",
119
+ "PlayerNotFoundError",
120
+ "PlayerPunishments",
121
+ "PlayerSearchResult",
122
+ "PlayerStatus",
123
+ "PlayerSummary",
124
+ "Proficiency",
125
+ "ProficiencyResponse",
126
+ "ProfileCard",
127
+ "PunishmentEntry",
128
+ "PunishmentRecord",
129
+ "PunishmentsPage",
130
+ "RankRecord",
131
+ "RivalsDataClient",
132
+ "RivalsDataError",
133
+ "RivalsDataHTTPError",
134
+ "StatRecord",
135
+ "TeamScore",
136
+ "TeamUpRecord",
137
+ "TeamUpsResponse",
138
+ "Teammate",
139
+ "TierListResponse",
140
+ "Top500Player",
141
+ "Top500Response",
142
+ "Top500Season",
143
+ "XPPage",
144
+ "XPRecord",
145
+ "hero_id",
146
+ "hero_name",
147
+ ]
148
+ __version__ = "1.1.0"
@@ -7,6 +7,7 @@ from the site's public player page and search box.
7
7
  from __future__ import annotations
8
8
 
9
9
  import json
10
+ import time
10
11
  import unicodedata
11
12
  from typing import Any
12
13
  from urllib.parse import urlencode
@@ -14,7 +15,7 @@ from urllib.parse import urlencode
14
15
  from curl_cffi import requests
15
16
 
16
17
  from .exceptions import CloudflareError, PlayerNotFoundError, RivalsDataHTTPError
17
- from .models import Player
18
+ from .models import Player, PlayerSearchResult
18
19
  from .resources import (
19
20
  Factions,
20
21
  Favorites,
@@ -33,6 +34,15 @@ class RivalsDataClient:
33
34
  base_url = "https://rivalsdata.com"
34
35
  api_url = "https://api.rivalsdata.com"
35
36
 
37
+ leaderboards: Leaderboards
38
+ heroes: HeroStats
39
+ team_ups: TeamUps
40
+ insights: Insights
41
+ factions: Factions
42
+ profiles: Profiles
43
+ favorites: Favorites
44
+ matches: Matches
45
+
36
46
  def __init__(
37
47
  self,
38
48
  *,
@@ -71,7 +81,19 @@ class RivalsDataClient:
71
81
  def __exit__(self, *_: object) -> None:
72
82
  self.close()
73
83
 
74
- def resolve_player(self, username: str) -> dict[str, Any]:
84
+ def _request_with_gateway_retries(
85
+ self, method: Any, path: str, **kwargs: Any
86
+ ) -> Any:
87
+ """Retry short-lived upstream gateway failures on read-only requests."""
88
+ url = f"{self.api_url}{path}"
89
+ for attempt in range(3):
90
+ response = method(url, timeout=self.timeout, **kwargs)
91
+ if response.status_code not in (502, 504) or attempt == 2:
92
+ break
93
+ time.sleep(0.25 * (attempt + 1))
94
+ return response
95
+
96
+ def resolve_player(self, username: str) -> PlayerSearchResult:
75
97
  """Search by username; add numeric uid to the source result."""
76
98
  query = username.strip()
77
99
  if not query:
@@ -95,7 +117,7 @@ class RivalsDataClient:
95
117
  raise RivalsDataHTTPError(
96
118
  "Search returned a player without a recognizable numeric UID"
97
119
  )
98
- return {**player, "uid": uid}
120
+ return PlayerSearchResult({**player, "uid": uid})
99
121
 
100
122
  def get_player(self, uuid_or_username: str | int) -> Player:
101
123
  """Fetch a typed public profile by numeric UID or username.
@@ -120,8 +142,8 @@ class RivalsDataClient:
120
142
 
121
143
  def _get_json(self, path: str, *, params: dict[str, Any] | None = None) -> Any:
122
144
  query = {key: value for key, value in (params or {}).items() if value is not None}
123
- response = self.session.get(
124
- f"{self.api_url}{path}", params=query, timeout=self.timeout
145
+ response = self._request_with_gateway_retries(
146
+ self.session.get, path, params=query
125
147
  )
126
148
  body = response.text
127
149
  blocked = (
@@ -190,8 +212,8 @@ class RivalsDataClient:
190
212
  ) from exc
191
213
 
192
214
  def _post_json(self, path: str, payload: dict[str, Any]) -> Any:
193
- response = self.session.post(
194
- f"{self.api_url}{path}", json=payload, timeout=self.timeout
215
+ response = self._request_with_gateway_retries(
216
+ self.session.post, path, json=payload
195
217
  )
196
218
  body = response.text
197
219
  blocked = (