Back to the catalog

start.gg MCP Server

MCP server for the start.gg GraphQL API: tournaments, events, entrants, sets, standings, streams

Open source Open in the app JSON README (API)

About

MCP server for the start.gg GraphQL API: tournaments, events, entrants, sets, standings, streams

Details

Kind
MCP servers
Topic
Developer tools
Publisher
tomo789
Origin
official
Category
ferramentas
Transport
local
Version
0.1.0
Open pull requests
2
Last push
2026-09-02T05:30:25Z
Repository state
ativo
Language
TypeScript
License
MIT
Added
2026-08-29 04:01:33
Updated
2026-08-29 04:01:33
Origin id
io.github.tomo789/startgg-mcp-server

README

# startgg-mcp-server

A [Model Context Protocol](https://modelcontextprotocol.io) server for the
[start.gg](https://www.start.gg) GraphQL API. It lets MCP clients (Claude Code,
Claude Desktop, and others) discover tournaments, inspect events, entrants,
sets, standings, and streams for **any game on start.gg** using natural
language.

## What is this?

start.gg exposes a powerful but complex GraphQL API: entrants vs participants
vs players, integer set states, complexity-limited pagination, epoch
timestamps. This server wraps that API in a small set of MCP tools with:

- **Normalized output** — sets come back as `{ round, state: "COMPLETED", entrant1: { gamerTag, seed }, score, winnerEntrantId, ... }` instead of raw GraphQL nesting
- **URL resolution** — paste a start.gg URL, get tournament/event ids back
- **Built-in rate limiting, retries, and caching** tuned to start.gg's documented limits

The server is game-agnostic. Game-specific logic (e.g. Smash upset detection)
belongs in applications built on top — see
[`examples/smash-ultimate-watcher`](examples/smash-ultimate-watcher/).

## Features

- 15 read-only tools covering discovery, tournaments, events, players, streams, and URL resolution
- Input validation (Zod) on every tool — bad ids, oversized page sizes, and malformed URLs never reach the API
- Sliding-window rate limiter (default 75 req/60s vs start.gg's 80), retries with exponential backoff, and `Retry-After` support
- Short-TTL in-memory cache for metadata queries
- Typed error codes: `AUTH_ERROR`, `RATE_LIMITED`, `NOT_FOUND`, `INVALID_INPUT`, `STARTGG_GRAPHQL_ERROR`, `NETWORK_ERROR`, `INTERNAL_ERROR`
- GraphQL documents kept in [`graphql/`](graphql/) files, separate from code
- The API token never appears in output, logs, or error messages

## Requirements

- Node.js >= 22
- A start.gg API token

## Getting a start.gg API token

1. Log in to start.gg
2. Open **[developer settings](https://start.gg/admin/profile/developer)** (Profile → Developer Settings)
3. Create a personal access token and copy it

Treat the token like a password. This server reads it only from the
`STARTGG_TOKEN` environment variable.

## Installation

```bash
git clone https://github.com/tomo789/startgg-mcp-server.git
cd startgg-mcp-server
npm install
npm run build
```

## MCP client setup

### Claude Code (CLI)

```bash
claude mcp add startgg --env STARTGG_TOKEN=YOUR_TOKEN -- node /path/to/startgg-mcp-server/dist/cli.js
```

### Claude Desktop

Add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "startgg": {
      "command": "node",
      "args": ["/path/to/startgg-mcp-server/dist/cli.js"],
      "env": {
        "STARTGG_TOKEN": "YOUR_TOKEN"
      }
    }
  }
}
```

Any MCP client that supports stdio servers works the same way: run
`node dist/cli.js` (or the `startgg-mcp-server` bin once installed via npm)
with `STARTGG_TOKEN` set.

## Available tools

### Discovery

| Tool                           | Purpose                                                                                                 |
| ------------------------------ | ------------------------------------------------------------------------------------------------------- |
| `search_videogames`            | Find videogame ids by name (e.g. "Super Smash Bros. Ultimate" → 1386)                                   |
| `search_tournaments`           | General tournament search: name, videogame, country/state, date range, upcoming/past, open registration |
| `get_upcoming_tournaments`     | Tournaments that haven't ended yet (includes in-progress), soonest first, with a days window            |
| `get_tournaments_by_videogame` | Tournaments for one videogame id (upcoming / past / all)                                                |

### Tournament

| Tool                      | Purpose                                                                                    |
| ------------------------- | ------------------------------------------------------------------------------------------ |
| `get_tournament`          | Details, schedule, venue, events list, configured streams                                  |
| `get_tournament_events`   | Events (brackets) of a tournament, optionally filtered by videogame                        |
| `get_tournament_entrants` | Tournament-level participants (attendees); per-event seeding lives in `get_event_entrants` |
| `get_stream_queue`        | Stream queue: streams (with derived Twitch URLs) and the sets assigned to each             |

### Event

| Tool                  | Purpose                                                                |
| --------------------- | ---------------------------------------------------------------------- |
| `get_event`           | Event details including phases (Pools, Top 8, ...) with phase ids      |
| `get_event_entrants`  | Entrants with seed, players, DQ flag; pagination or `fetchAll`         |
| `get_event_standings` | Placements (use `perPage: 8` for Top 8)                                |
| `get_event_sets`      | Normalized sets; filter by state, phase, round, entrants, VOD presence |

### Player

| Tool              | Purpose                                      |
| ----------------- | -------------------------------------------- |
| `get_player`      | Player by id: gamer tag, prefix, linked user |
| `get_player_sets` | A player's recent sets across tournaments    |

### Utility

| Tool                  | Purpose                                                             |
| --------------------- | ------------------------------------------------------------------- |
| `resolve_startgg_url` | start.gg URL/slug → `{ type, tournamentId, eventId, slugs, names }` |

Tournament/event tools accept **either** a numeric id, a slug, or a full
start.gg URL — you rarely need `resolve_startgg_url` explicitly, but it is
there when you want the ids.

### Normalized set shape

```json
{
  "id": 106877974,
  "round": "Grand Final",
  "roundNumber": 3,
  "state": "COMPLETED",
  "stateRaw": 3,
  "completedAt": "2026-08-24T07:19:34.000Z",
  "entrant1": {
    "entrantId": 24480092,
    "name": "LittleMacMain",
    "seed": 5,
    "players": [{ "playerId": 3655189, "gamerTag": "LittleMacMain", "prefix": "" }],
    "score": 2
  },
  "entrant2": { "...": "same shape" },
  "score": { "entrant1": 2, "entrant2": 3, "displayScore": "LittleMacMain 2 - RenSuø 3" },
  "winnerEntrantId": 24481002,
  "phase": { "id": 1994001, "name": "Bracket" },
  "vodUrl": null
}
```

Notes grounded in the live API:

- `roundNumber < 0` means losers bracket; `round` is the human-readable name
- a score of `-1` is start.gg's disqualification marker
- unstarted "preview" sets have **string** ids like `"preview_3430499_2_0"`
- `state` names are decoded from the integer `stateRaw`; both are always returned
- `entrant1`/`entrant2` use a `players` array, so doubles/teams work unchanged

## Examples

Things to ask an MCP client once connected:

```text
Find upcoming Super Smash Bros. Ultimate tournaments this week.

Get the entrants and seeds for this start.gg tournament URL:
https://www.start.gg/tournament/.../event/...

Show me completed sets from Top 8 of that event.

Which streams are assigned to sets at this tournament?

What were the biggest seed upsets in this event?
```

A standalone example application (videogame lookup → upcoming tournaments →
sets → upset candidates by seed difference) lives in
[`examples/smash-ultimate-watcher`](examples/smash-ultimate-watcher/).

## Environment variables

| Variable                | Required | Default | Purpose                                                         |
| ----------------------- | -------- | ------- | --------------------------------------------------------------- |
| `STARTGG_TOKEN`         | yes      | —       | start.gg API token                                              |
| `STARTGG_ENABLE_WRITES` | no       | `false` | Reserved. No write tools exist yet; the flag only logs a notice |
| `STARTGG_RATE_LIMIT`    | no       | `75`    | Requests per 60s window (hard-capped at 80)                     |
| `STARTGG_TIMEOUT_MS`    | no       | `30000` | Per-request HTTP timeout                                        |
| `STARTGG_CACHE`         | no       | `on`    | Set `off` to disable the in-memory cache                        |

The API endpoint is deliberately not configurable through the environment: the
token is only ever sent to `api.start.gg`. When using the client as a library
(tests, tooling), inject `apiUrl`/`fetchFn` via the `StartggClient` constructor.

Without `STARTGG_TOKEN` the server still starts and lists tools, but every
call returns a clear `AUTH_ERROR` explaining how to fix it.

## Security

- The token is read from the environment only, sent only to `api.start.gg`, and never included in tool output, logs, or error messages
- All tools are read-only; no mutations are implemented
- `.env` files are git-ignored; use `.env.example` as a template
- User-supplied input is schema-validated before any request is built

## Rate limits

start.gg allows **80 requests per 60 seconds** and at most **1000 objects per
request**. This server:

- keeps a sliding-window budget below the request limit (default 75/60s)
- retries `429` (honoring `Retry-After`) and transient 5xx errors with exponential backoff, at most 3 retries — GraphQL errors are never retried
- caps `perPage` per tool so responses stay under the 1000-object complexity limit (sets are expensive: ~26+ objects each, hence `perPage <= 30`)
- caps `fetchAll` at a fixed page budget and reports `truncated: true` when it stops early

## Development

```bash
npm run dev        # run from source (tsx)
npm run build      # compile to dist/
npm run typecheck  # tsc --noEmit
npm run lint       # eslint
npm run format     # prettier
```

GraphQL documents live in `graphql/*.graphql` (one file per domain, multiple
named operations per file; requests select an operation via `operationName`).
Schema facts verified against the live API are recorded in
[`docs/startgg-api-notes.md`](docs/startgg-api-notes.md) — read it before
adding fields.

## Testing

```bash
npm test                    # unit tests (fixtures/mocks only, no network)
STARTGG_INTEGRATION=1 STARTGG_TOKEN=... npm test   # + 2 live API smoke tests
STARTGG_TOKEN=... node scripts/smoke.mjs           # full stdio end-to-end smoke (~10 live requests)
```

Unit tests cover the URL resolver, normalizers, input validation, pagination,
GraphQL/HTTP error handling, the rate limiter, and the cache.

## License

[MIT](LICENSE)

More