
esportsodds.gg
io.github.esportsodds由 GitHub 帳號 esportsodds 發布v0.4.5更新於 Nov 3, 2026
面向AI助理的CS2比賽資料:比賽、地圖、停用選擇、回合、選手資料、戰隊、排名。
AI 翻譯由 AI 模型根據下方的原文描述翻譯,可能有誤。
原文描述
CS2 match data for AI assistants: matches, maps, vetoes, rounds, player stats, teams, rankings.
概覽
esportsodds.gg 的本機 MCP 服務;無需登入;STDIO。
暫無 AI 概覽:以下資訊來自註冊中心條目和服務本身。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 esportsodds.gg,將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
其他 MCP 客戶端
參照 儲存庫 中的啟動說明。
README
esportsodds
The official Python client for the esportsodds.gg CS2 data API.
Counter-Strike 2 matches and results, team and player stats, tournaments, rankings and a derived market line, returned as plain Python dicts. Standard library only: there is nothing else to install.
Get CS2 match data and stats without scraping HLTV. This package is a client for a paid API; it is not an official HLTV/Valve API.
Install
Python 3.10 or newer. No dependencies.
Try it first, with no account: set ESPORTSODDS_API_KEY to the public sample key eo_sample_public, which
reads a fixed sample of past matches. It is shared by everyone and rate-limited per network address.
For evaluation; our Terms apply.
No subscription yet? A free sandbox key reads a fixed
sample of past matches, so you can build against real responses first. The quickstart below asks
for upcoming matches, which are outside the sample, so with either key start with
sandbox_tour.py
and a tournament id from the guide. Any request outside the sample raises PermissionDeniedError
(sandbox_scope), and its message names the events that work.
Quickstart
Every call returns what the API sent, as ordinary dicts and lists. A list call returns a Page
(page.data, page.next_cursor, page.has_more, page.meta); a call for one thing returns a dict.
Matches come newest first unless you say otherwise, which is what you want for results. For
scheduled matches that order starts with the fixture furthest away, so the quickstart passes
sort="scheduled_at" to get the next ones to be played.
Your API key and the plan
There are two plans, billed monthly or yearly and paid up front: Standard is $99 a month for 50,000 requests
and Pro is $299 a month for 150,000 requests. A year costs ten months: $990 a year for Standard and
$2,990 a year for Pro, two months free. Requests are counted per account, per calendar month (UTC), across every
key the account owns. There is no trial. A free sandbox key
reads a fixed sample of past matches, up to 2,000 requests in total, with no card; it is not a plan, and
current data needs one. Past your plan's requests, calls get a 429 with the code quota_exceeded until the
month resets, and this client raises QuotaExceededError without retrying (retrying cannot help). A spent
sandbox key gets sandbox_quota_exceeded, raised the same way (it never resets), and a request
outside its sample raises PermissionDeniedError (sandbox_scope, not counted).
The bulk export (matches.bulk() and the walkers built on it) has one more limit. On a Standard account that first subscribed on or after 30 October 2026 you may make up to 250 bulk-export requests a month; past that the API
answers 429 with bulk_quota_exceeded, which raises QuotaExceededError without retrying and leaves every
other call working. Pro has no bulk limit, and a Standard account that first subscribed before that date has none either.
Create a key in your account after you subscribe, then keep it out of your code:
- The client sends the key only in the
Authorization: Bearerheader, never in a URL. It shows only the last four characters inrepr()and never puts the key in an error message or a log line. - It talks HTTPS only. Plain
http://is accepted forlocalhostand other loopback addresses (127.0.0.1,::1) alone, and redirects are never followed, so a key cannot be sent to a different host. - Keep the key on your server. A browser or mobile app should call your backend, not the API.
- You can look at real responses before subscribing on the public demo.
Configuration
A Client is safe to share between threads. Use it as a context manager (with Client() as client:)
or call client.close() when you are finished: it closes a custom transport that has a close()
method (the default one keeps no connections open, so it has nothing to release).
Methods
Every endpoint is one method. {game} is the client's game, and {id} accepts the UUID or, for
teams, players and tournaments, the slug.
Filters are keyword arguments named as in the API reference.
A list given for a comma-separated filter (has=["rounds", "depth"], include=["maps", "stats"])
is joined for you; a datetime or date is formatted for you (a naive datetime means UTC); an
unknown argument raises TypeError instead of being sent. Ids are checked and escaped before they
go into a URL.
The odds are our own derived lines (source="eo_market"), not any bookmaker's price. The model
line (source="eo_model") is coming soon and is not returned yet.
Pagination, the bulk export and the change feed
List calls return one page and a cursor. Walk them yourself:
or let the client do it. iter() yields rows, pages() yields pages, and both fetch lazily, one
request per page, so stopping early costs nothing more. max_pages is a safety limit; the last page
yielded still carries its next_cursor, so a job that stops (or crashes) can resume with cursor=.
iter and pages exist on matches, odds, teams, tournaments and players.
The bulk export is the cheap way to pull history: 25 matches per request, each with the sub-resources you ask for embedded, and one request against your allowance per page.
max_pages stops the walk after that many requests. Without it, iter_bulk keeps going until the filter runs out, which for a wide
one is the whole history, one request per page. On a Standard subscription with the 250-a-month limit, a long walk
stops with bulk_quota_exceeded when the month's requests are used. Walk with pages_bulk() and save each page's
next_cursor, and the walk resumes with cursor= after the month resets.
A busy moment answers 503 with a short Retry-After; the client waits and retries for you.
The change feed keeps a local copy current. Give iter_changes the changed_at of the newest
row you stored last time and it yields every match that changed since, oldest change first:
It starts ten minutes before since (overlap=) because a row can become visible shortly after the
changed_at it carries, and drops the repeats this causes. A quiet hour costs one request.
matches.history(match) and matches.iter_history(match) show what each of those changes was.
Errors
Every error is an EsportsOddsError. A reply with an error status raises an ApiError subclass;
branch on the class, or on error.code, never on error.message.
An unknown path id is a 404 (NotFoundError), not a 400, and an unknown id on a sub-resource
such as matches.maps(...) returns an empty page rather than an error.
An ApiError carries status, code, message, request_id, retry_after, headers and body.
Quote request_id when you report a problem; it is also in client.last_response.request_id.
Rate limits and retries
Two limits apply: a per-key token bucket (20 requests a second sustained, bursts of 100) and
the monthly allowance of your plan, 50,000 requests on Standard or 150,000 on Pro, per account,
shared by all of an account's keys. After every
call, client.last_response.rate_limit has the budget the API reported:
The client retries only what is safe to retry, up to max_retries times (3 by default), and logs
each retry at INFO on the esportsodds logger:
- A
429 rate_limitedor503waits theRetry-Afterthe API sent, plus up to half a second of jitter. A wait longer than a minute is raised instead of slept through. - A
502or504and a network failure back off from half a second, doubling, up to eight seconds, with jitter. - A
500is retried at most twice however highmax_retriesis: the API counts it against your allowance, so each retry spends a request you are paying for. - A
429 quota_exceededis never retried, and nothing else is.
Polling is rarely the cheapest design: the change feed and the WebSocket cost far fewer requests. The request budget guide has the numbers.
WebSocket (match updates)
The WebSocket pushes score and odds changes for the matches you subscribe to, and it costs no requests: neither the ticket, nor the connection, nor the messages count against your allowance. Updates arrive when our own collection sees a change, so the socket removes your polling delay but cannot make the data newer than that collection; the guide linked below lists the typical ages.
This package does not open sockets itself (that would need a dependency). It mints the ticket and
builds the address; any WebSocket library connects, for example
websockets (pip install websockets):
- A ticket lasts 60 seconds and is not single-use, so a retry inside that window can reuse it; mint a new one once it has expired. Your key never appears in the socket address.
- A
snapshotis the full current state of a match and anupdateis a change.seqcounts up per match; if one is skipped, subscribe to that match again to get a freshsnapshot. - Reconnect with exponential backoff and jitter, and re-send your
subscribe. There is no replay. - A key may hold five connections at once, so subscribe to many matches on one connection.
- The server pings every 54 seconds;
websocketsanswers for you.
The WebSocket guide lists every frame type.
AI assistants (MCP)
The package includes an MCP (Model Context Protocol) server, so an AI assistant such as Claude or
Cursor can answer questions from the API: a team's recent form, the vetoes of a match, a
player's career figures. It runs on your machine, reads ESPORTSODDS_API_KEY and only reads data.
Each tool call is one request against your allowance, and the list tools ask for 20 rows unless the
assistant sets limit.
Add it to your MCP client's configuration (Claude Desktop: claude_desktop_config.json; Cursor:
~/.cursor/mcp.json). With uv installed, uvx fetches the package itself, so there is nothing
else to set up:
Or pip install esportsodds and use esportsodds-mcp as the command, with no args (give its
full path, from which esportsodds-mcp, if the client cannot find it). In Claude Code:
The tools are matches_list, matches_get, matches_maps, matches_stats, matches_vetoes,
matches_rounds, matches_lineups, odds_list, teams_list, teams_get, teams_h2h,
teams_form, teams_maps, teams_results, teams_players, tournaments_list,
tournaments_get, tournaments_teams, players_list, players_get, players_career,
players_matches, rankings_list and maps_list. Each is one row of the methods table above,
with that method's arguments, and the server answers clients of every MCP revision from
2024-11-05 to 2026-07-28.
Examples
The examples/ folder has
runnable scripts. Each reads ESPORTSODDS_API_KEY.
FAQ
Is this an official API? No. It is not an official HLTV/Valve API, and esportsodds.gg is not affiliated with or endorsed by Valve or any stats site.
Why not scrape? Scraping breaks whenever a page changes and many sites forbid it. With this API you build without scraping HLTV or anyone else: you call stable, documented endpoints instead.
Is there a free tier? Not for current data. There is no trial; Standard is $99 a month for 50,000 requests and Pro is $299 a month for 150,000, paid up front. A free sandbox key reads a fixed sample of past matches, and the public demo shows real responses without an account.
What does a call cost? One request against your monthly allowance, whatever it returns. A bulk page, which
carries up to 25 matches with their detail, is also one request (and, on a Standard account that first subscribed on or after 30 October 2026, one of its 250 bulk-export requests a month). A walker (iter, pages,
iter_bulk, iter_changes) costs one request per page it fetches. The WebSocket costs none.
Does it work with asyncio? The client is synchronous and safe to share between threads. From async code, run a call in a thread:
Can I use another HTTP library? Yes. Pass transport= any object with a
send(TransportRequest) -> TransportResponse method. To go through a proxy with the default
transport, pass UrllibTransport(proxies={"https": "http://proxy.example:3128"}).
Which games are supported? CS2 (game="cs2"). The game argument exists for later titles.
Is it typed? Yes: the package ships py.typed and passes mypy --strict. Responses are plain
dicts, so they are dict[str, Any].
Where do I report a bug in this client? In this repository's issues. Questions about your account, billing or the data go through esportsodds.gg.
Licence
The code in this repository is released under the MIT licence. The licence covers this client, not the data it fetches: the data is licensed to you under sections 4 and 5 of the Terms of Service, which allow you to build on it for your own purposes and do not allow redistributing it.
Counter-Strike and CS2 are trademarks of Valve Corporation. esportsodds.gg is independent and is not affiliated with, endorsed by or sponsored by Valve.
來源:README.md,提交 973c5cb
工具
0版本歷史
1- v0.4.5最新Nov 3, 2026
評分
已滿足適用檢查項的 50%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通25 分
請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。
- 已宣告認證方式10 分
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分