faceit-mcp
<div align="center">
# faceit-mcp
[](https://www.python.org/)
[](https://pypi.org/project/faceit-mcp/)
[](./LICENSE)
[](https://modelcontextprotocol.io/)
[](https://docs.faceit.com/docs/data-api/data-api/)
**Give your AI assistant typed, first-class access to the FACEIT platform.**
An [MCP](https://modelcontextprotocol.io/) server that maps the [FACEIT Data API v4](https://docs.faceit.com/docs/data-api/data-api/) to a curated set of tools—so agents can query players, matches, hubs, tournaments, and more with clear, disambiguated intent.
</div>
---
## Features
- **Full API coverage** — All **64** `GET` endpoints from the official Swagger spec, generated into per-tag Python modules and registered with [FastMCP](https://github.com/modelcontextprotocol/python-sdk).
- **Agent-friendly tool design** — Every tool carries an enriched description with _“use this when / prefer that when”_ hints so models pick the right call among similar names (for example `searchPlayers` vs `getPlayerFromLookup` vs `getPlayer`, or lifetime vs per-match player stats).
- **Typed request surfaces** — Tool parameters are generated from the spec with accurate Python annotations (paths, query strings, enums, and pagination limits).
- **Production-ready HTTP** — Async [httpx](https://www.python-httpx.org/) client with bearer auth, timeouts, and structured errors tuned for LLM-facing messages (401 / 403 / 404 / 429 / 503).
- **Reproducible codegen** — Tools are generated from `swagger.json` via `scripts/generate_tools.py`; committed output means the server runs without the spec at runtime.
---
## Requirements
- **Python** 3.10+ (tested on 3.11)
- A **server-side** FACEIT API key from [developers.faceit.com](https://developers.faceit.com) (My Apps → API Keys → Server-side)
---
## Installation
### From PyPI
Ideal for dev containers, CI, and machines where you do not need a git clone:
```bash
pip install faceit-mcp
```
With [uv](https://docs.astral.sh/uv/):
```bash
uv tool install faceit-mcp
```
Run once without a global install (also suitable for Cursor):
```bash
uvx faceit-mcp
```
### From source (contributors)
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
```
With [uv](https://docs.astral.sh/uv/) (this repo ships an `uv.lock`):
```bash
uv venv && source .venv/bin/activate
uv pip install -e .
```
---
## Configuration
Copy the example env file and set your key:
```bash
cp .env.example .env
# Edit .env and set FACEIT_API_KEY=...
```
| Variable | Required | Default | Description |
| --------------------- | -------- | --------------------------------- | ---------------------------------------------------- |
| `FACEIT_API_KEY` | yes | — | Server-side key (sent as `Authorization: Bearer …`). |
| `FACEIT_API_BASE_URL` | no | `https://open.faceit.com/data/v4` | Override for testing or staging. |
| `FACEIT_API_TIMEOUT` | no | `30` | HTTP timeout in seconds. |
---
## Run (stdio)
This server uses **stdio** transport (JSON-RPC over stdin/stdout). Cursor, Claude Desktop, or the MCP Inspector spawn the process and attach to that pipe. If you run `faceit-mcp` in a normal shell with no client, it will appear idle because it is waiting for MCP messages, not printing a REPL.
```bash
faceit-mcp
# equivalent:
python -m faceit_mcp
```
**Quick smoke test** (no HTTP; verifies all tools register):
```bash
FACEIT_API_KEY=placeholder python -c "
import asyncio
from faceit_mcp.server import mcp
tools = asyncio.run(mcp.list_tools())
assert len(tools) == 64, len(tools)
print('OK', len(tools))
"
```
---
## Try it with the MCP Inspector
The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is a browser UI for exploring and calling a live MCP server. The `mcp[cli]` dependency provides `mcp dev`, which launches the Inspector and connects to this server over stdio.
**Prerequisites**
- Node.js on `PATH` (Inspector is loaded via `npx`; see the Inspector repo for supported versions)
- [`uv`](https://docs.astral.sh/uv/) on `PATH` (used by `mcp dev` for an isolated env)
- `FACEIT_API_KEY` available to the server (e.g. via `.env` at the repo root, loaded by `config.py`)
**Launch** (venv active so `mcp` resolves):
```bash
source .venv/bin/activate
mcp dev src/faceit_mcp/server.py:mcp --with-editable .
```
Or call the binary directly:
```bash
./.venv/bin/mcp dev src/faceit_mcp/server.py:mcp --with-editable .
```
**What happens**
- `uv` creates a temporary environment and installs this project in editable mode.
- The `mcp` instance in `src/faceit_mcp/server.py` loads.
- The Inspector UI is served (typically `http://localhost:6274/`; proxy on `:6277`). Open the printed URL (includes `MCP_PROXY_AUTH_TOKEN`) or paste the token into the UI.
**Smoke sequence in the UI**
1. Connect (often automatic).
2. Open **Tools** — you should see all 64 `faceit_*` tools with full descriptions.
3. Try: `faceit_getAllGames` → `faceit_searchPlayers` (`nickname: "s1mple"`) → `faceit_getPlayer` → `faceit_getPlayerLifetimeStats` / `faceit_getPlayerRecentMatchStats` to compare aggregates vs per-match stats.
Stop with `Ctrl+C` in the terminal.
**Troubleshooting**
- **401:** Missing or invalid `FACEIT_API_KEY` — check `.env` or export the variable before `mcp dev`.
- **`ImportError: attempted relative import with no known parent package`:** Use current `server.py` with absolute imports, or reinstall with `pip install -e .`.
- **`EADDRINUSE` on 6274/6277:** Another Inspector is running — stop it or `pkill -f '@modelcontextprotocol/inspector'`.
### Alternative: Inspector via `npx`
You can run the Inspector package directly; it spawns your server as a child process. Replace the Python path with your venv or use `faceit-mcp` if it is already on `PATH`:
```bash
npx @modelcontextprotocol/inspector -e FACEIT_API_KEY=YOUR_KEY_HERE -- /absolute/path/to/faceit-mcp/.venv/bin/python -m faceit_mcp
```
```bash
npx @modelcontextprotocol/inspector -e FACEIT_API_KEY=YOUR_KEY_HERE -- faceit-mcp
```
Open the URL printed in the terminal (it includes the proxy session token), then use **Tools** to list and call `faceit_*` tools.
---
## Use in Cursor, Claude Desktop, or Claude Code
All of these clients use the same shape of **stdio** config: a command, optional args, and environment variables. The server does not open its own network port for MCP.
### Cursor
Put this server in **user** MCP config only, not in the project’s `.cursor/mcp.json`. The FACEIT API key belongs in `env` next to the server entry; workspace files are easy to commit, share, or check into git by mistake. User config stays on your machine and keeps the secret out of the repo.
1. Open **Cursor Settings → MCP** (or edit the JSON manually).
2. Edit **user** config:
- **macOS / Linux:** `~/.cursor/mcp.json`
- **Windows:** `%USERPROFILE%\.cursor\mcp.json`
Merge the `faceit` block into the existing `mcpServers` object if you already have other servers.
**Recommended (PyPI)** — `uvx` creates an isolated environment when Cursor starts the server:
```json
{
"mcpServers": {
"faceit": {
"command": "uvx",
"args": ["faceit-mcp"],
"env": {
"FACEIT_API_KEY": "YOUR_KEY_HERE"
}
}
}
}
```
If `faceit-mcp` is already on `PATH`:
```json
{
"mcpServers": {
"faceit": {
"command": "faceit-mcp",
"env": {
"FACEIT_API_KEY": "YOUR_KEY_HERE"
}
}
}
}
```
**Local clone** — point `command` at the venv script:
```json
{
"mcpServers": {
"faceit": {
"command": "/absolute/path/to/faceit-mcp/.venv/bin/faceit-mcp",
"env": {
"FACEIT_API_KEY": "YOUR_KEY_HERE"
}
}
}
}
```
**Local `uvx` without PyPI:**
```json
{
"mcpServers": {
"faceit": {
"command": "uvx",
"args": ["--from", "/absolute/path/to/faceit-mcp", "faceit-mcp"],
"env": { "FACEIT_API_KEY": "YOUR_KEY_HERE" }
}
}
}
```
After saving, enable the **faceit** server in the MCP panel if it is not already on. New chats in Agent mode can then use the FACEIT tools.
If you use a **dev container** or remote SSH, Cursor may resolve `command` on the remote side; still keep the entry in **user** `mcp.json` so the key is not stored in the project. See Cursor’s docs for how MCP runs in your setup.
### Claude Desktop
1. Quit Claude Desktop completely.
2. Edit the MCP config file for your OS (create it if missing):
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
3. Merge a `mcpServers` entry with the same JSON shape as in the Cursor examples above (for example `uvx` + `faceit-mcp` and `FACEIT_API_KEY` in `env`).
4. Start Claude Desktop again; the FACEIT tools should appear under MCP for supported conversations.
### Claude Code
Prefer **user** scope (or the equivalent global config) so `FACEIT_API_KEY` is not stored in a committed project file. Example:
```bash
claude mcp add --transport stdio --scope user --env FACEIT_API_KEY=YOUR_KEY_HERE faceit -- uvx faceit-mcp
```
`--scope user` keeps the definition (and env) in user config rather than a project `.mcp.json` you might commit. See [Connect Claude Code to tools via MCP](https://code.claude.com/docs/en/mcp).
---
## Development
- **Hand-written code:** `src/faceit_mcp/{server,client,config,errors}.py` and `src/faceit_mcp/__main__.py` — safe to edit.
- **Generated code:** everything under `src/faceit_mcp/tools/` comes from `scripts/generate_tools.py` — do not edit by hand. See [AGENTS.md](./AGENTS.md) for the full workflow (hints, overrides, regeneration, and the tool-count smoke test).
- **Setup:** clone the repo, create a venv, `pip install -e .` or `uv pip install -e .`, copy `.env.example` to `.env`, set `FACEIT_API_KEY`.
- **Manual QA:** run `mcp dev src/faceit_mcp/server.py:mcp --with-editable .` or the `npx @modelcontextprotocol/inspector` commands above and exercise a few tools against the real API.
- **Publishing / versioning:** see **Publishing to PyPI** below.
There is no bundled pytest/ruff config yet; the smoke snippet in the **Run (stdio)** section above is the lightweight regression check documented in [AGENTS.md](./AGENTS.md).
---
## Dev container (e.g. TypeScript repo)
Add Python to the container and install the MCP package once. Configure **Cursor user** `~/.cursor/mcp.json` (not a tracked workspace file) with `"command": "/opt/faceit-mcp/bin/faceit-mcp"` or `"faceit-mcp"` once that binary is on `PATH` in the environment where Cursor launches MCP—same rule as above: **do not** put `FACEIT_API_KEY` in a project-level `.cursor/mcp.json` that could be committed.
Example `.devcontainer/devcontainer.json` fragment — a venv under `/opt` avoids Debian PEP 668 “externally managed environment” issues:
```json
{
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
"features": {
"ghcr.io/devcontainers/features/python:1": {
"version": "3.11"
}
},
"postCreateCommand": "python3 -m venv /opt/faceit-mcp && /opt/faceit-mcp/bin/pip install -U pip faceit-mcp"
}
```
Supply `FACEIT_API_KEY` only in **user** Cursor MCP `env`, `containerEnv`, or a gitignored `devcontainer.env` — never commit real keys.
For MCP running **inside** the container, installing `faceit-mcp` in `postCreateCommand` is more reliable than assuming `uvx` on the host. Alternatively, keep a Node-only image and use **`uvx faceit-mcp`** if your setup launches MCP where Python is available.
---
## Publishing to PyPI (maintainers)
1. [PyPI account](https://pypi.org/account/register/) and an [API token](https://pypi.org/manage/account/token/) (or trusted publishing from GitHub).
2. Bump `version` in `pyproject.toml` for each release.
3. Build and upload:
```bash
uv build
UV_PUBLISH_TOKEN=pypi-xxxxxxxx uv publish
```
Or: `python -m build` and `twine upload dist/*`.
4. After the first upload, the PyPI-based Cursor snippets work for everyone.
Project on PyPI: [`faceit-mcp`](https://pypi.org/project/faceit-mcp/) — if the name is taken, rename `[project] name` in `pyproject.toml` and update docs.
---
## Tool catalogue (64 tools, 13 tags)
| Tag | Count | Highlights |
| ------------- | ----- | ------------------------------------------------------------------------------------------------------------- |
| Championships | 5 | `getChampionships`, `getChampionshipMatches`, `getChampionshipResults` |
| Games | 8 | `getAllGames`, `getGame`, `getQueuesByEntityFilters` |
| Hubs | 6 | `getHub`, `getHubMatches`, `getHubMembers` |
| Leaderboards | 7 | `getHubRanking`, `getHubSeasonRanking`, `getChampionshipGroupRanking` |
| Leagues | 3 | `getLeagueById`, `getLeagueSeason`, `getPlayerForLeagueSeason` |
| Matches | 2 | `getMatch`, `getMatchStats` |
| Matchmakings | 1 | `getMatchmaking` |
| Organizers | 6 | `getOrganizer`, `getOrganizerChampionships`, `getOrganizerTournaments` |
| Players | 9 | `getPlayer`, `getPlayerFromLookup`, `getPlayerLifetimeStats`, `getPlayerRecentMatchStats`, `getPlayerHistory` |
| Rankings | 2 | `getGlobalRanking`, `getPlayerRanking` |
| Search | 7 | `searchPlayers`, `searchTeams`, `searchChampionships`, … |
| Teams | 3 | `getTeam`, `getTeamStats`, `getTeamTournaments` |
| Tournaments | 5 | `getTournament`, `getTournamentBrackets`, `getTournamentMatches` |
Tools are registered with a `faceit_` prefix, e.g. `faceit_getPlayer`. The two operations that shared `getPlayerStats` in Swagger are:
- **`faceit_getPlayerLifetimeStats`** — career aggregates (`GET /players/{player_id}/stats/{game_id}`)
- **`faceit_getPlayerRecentMatchStats`** — paginated per-match stats (`GET /players/{player_id}/games/{game_id}/stats`)
---
## Regenerating tools
Generated files under `src/faceit_mcp/tools/` are committed and must not be edited by hand.
1. Place an updated `swagger.json` at the repo root (gitignored).
2. Adjust `TAG_HINTS` or `OPERATION_OVERRIDES` in `scripts/generate_tools.py` if needed.
3. Run:
```bash
python scripts/generate_tools.py
```
4. Commit the regenerated modules.
In Cursor you can use the `/regenerate-tools` command (see `.cursor/commands/regenerate-tools.md`).
---
## Project layout
```
faceit-mcp/
├── pyproject.toml
├── uv.lock
├── swagger.json # gitignored; build-time input only
├── scripts/generate_tools.py # swagger.json → tool modules
├── src/faceit_mcp/
│ ├── server.py # FastMCP app + stdio entrypoint
│ ├── client.py # httpx async client + auth + errors
│ ├── config.py # env-based settings
│ ├── errors.py # FaceitAPIError
│ └── tools/ # GENERATED; committed
│ └── <one module per swagger tag>.py
├── AGENTS.md
└── .cursor/commands/regenerate-tools.md
```
---
## License
The code in this repository is licensed under the terms in [LICENSE](./LICENSE). The FACEIT Data API, its Swagger specification, and API responses are the property of FACEIT LTD.
TDQS
Scored across 64 tools
Tools are largely distinct by entity (game, championship, hub, etc.) and action (get, search), with clear descriptions. Some overlap exists among ranking and leaderboard tools, but they are well-differentiated by parameters and context.
All tools follow a consistent pattern of 'faceit_verb_noun' (e.g., getPlayer, searchHubs). No mixing of casing styles or redundant prefixes. This makes the tool surface predictable.
64 tools is excessive for even a broad platform. While the FACEIT API covers many entities, the tool count overwhelms agents, with many niche tools (e.g., getChampionshipGroupRanking vs getLeaderboard) that could be combined.
The read-only coverage is fairly comprehensive across major entities (players, teams, matches, tournaments, hubs, championships), but there are notable gaps: no write operations, limited filtering on lists, and some missing endpoints like league season listing or live match data.