An MCP server for Teamarr. Lets Claude Desktop, Claude Code or any MCP client read and change your sports-EPG configuration: teams, event groups, templates, settings, channels and Dispatcharr sync.
Generated tier. At startup the server fetches your Teamarr's own /openapi.json and turns
every endpoint into an MCP tool with a clean name (list_teams, get_team, patch_team,
sync_lifecycle, ...). New Teamarr endpoints appear without a new release of this server. If
Teamarr is unreachable at startup, a vendored copy of the Teamarr 2.17.0 spec is used instead.
Curated tier. Hand-written tools that encode Teamarr API behaviour you would otherwise learn the hard way:
| Tool | What it does |
|---|---|
teamarr_info |
Instance URL, version, health, which spec was loaded, tool counts, active safety flags. Call it first if a tool seems missing. |
update_settings(block, changes, replace=False) |
Read-merge-write for any /api/v1/settings/<block>. See below. |
set_template_filler(section, field, text, template_ids=None) |
Set one pregame/postgame filler field across all event templates in one call. |
get_event_channels_summary() |
Live and upcoming event channels with number, event, league, source group and stream count, applied template, Dispatcharr id, tvg-id, sync status. |
find_unmatched_streams(group_id=None, reason=None, limit=200) |
Streams from the last EPG run that produced no channel, grouped by source group with that group's active regexes and timezone, for regex tuning. |
check_tsdb_gated_subscriptions() |
Subscribed leagues that return no fixtures because they need a TheSportsDB premium key. |
Why update_settings exists. Teamarr's PUT /api/v1/settings/<block> replaces the whole
block. Omit channel_range_start from a lifecycle PUT and it silently resets; omit epg_id or
default_channel_group_id from a Dispatcharr PUT and they are gone. update_settings GETs the
block, merges your changes over it, drops masked secrets so the server keeps them, and PUTs the
result. The raw settings PUT tools are therefore hidden. Pass replace=True if you really want
whole-block semantics. Teamarr's enum validation errors (e.g. Invalid channel_stability_mode. Valid: ['compact', 'gap', 'strict']) are passed through verbatim.
docker run -d --name teamarr-mcp -p 8000:8000 \
-e TEAMARR_URL=http://192.168.1.x:9195 \
ghcr.io/lukeeexd/teamarr-mcp:latestOr use docker-compose.example.yml. The MCP endpoint has no
authentication of its own, so only expose port 8000 to networks you trust; the compose example
binds to loopback by default.
Connect a client to http://<host>:8000/mcp:
# Claude Code
claude mcp add --transport http teamarr http://localhost:8000/mcp// Claude Desktop config or a project .mcp.json
{
"mcpServers": {
"teamarr": { "type": "http", "url": "http://localhost:8000/mcp" }
}
}uv tool install teamarr-mcp # or: pipx install teamarr-mcp# Claude Code
claude mcp add teamarr \
-e TEAMARR_URL=http://192.168.1.x:9195 \
-e TEAMARR_MCP_TRANSPORT=stdio \
-- teamarr-mcp// Claude Desktop config
{
"mcpServers": {
"teamarr": {
"command": "teamarr-mcp",
"env": {
"TEAMARR_URL": "http://192.168.1.x:9195",
"TEAMARR_MCP_TRANSPORT": "stdio"
}
}
}
}Claude Desktop config lives at ~/Library/Application Support/Claude/claude_desktop_config.json
(macOS), %APPDATA%\Claude\claude_desktop_config.json (Windows) or
~/.config/Claude/claude_desktop_config.json (Linux).
Everything is set through environment variables. Nothing is stored in client config except the URL of this server.
| Variable | Default | Notes |
|---|---|---|
TEAMARR_URL |
http://localhost:9195 |
Base URL of your Teamarr. |
TEAMARR_API_KEY |
unset | Sent only if set. Teamarr has no API auth today; this is future-proofing. |
TEAMARR_API_KEY_HEADER |
X-API-Key |
Header name used for the key. |
TEAMARR_MCP_TRANSPORT |
http |
http or stdio. |
TEAMARR_MCP_HOST |
0.0.0.0 |
HTTP transport bind address. |
TEAMARR_MCP_PORT |
8000 |
HTTP transport port. The MCP endpoint is /mcp. |
TEAMARR_MCP_ENABLE_DESTRUCTIVE |
false |
Expose deletes, backup restores, channel reset, cache clears. |
TEAMARR_MCP_READ_ONLY |
false |
Expose GET tools only. |
TEAMARR_OPENAPI_PATH |
unset | Load the spec from this file instead of the live instance. |
TEAMARR_MCP_LOG_LEVEL |
INFO |
Logs go to stderr. |
TEAMARR_MCP_EXCLUDE_PATHS |
unset | Comma-separated regexes; any API path matching one is never exposed, e.g. ^/api/v1/backup,/leagues/custom. |
Booleans accept 1, true, yes, on (case-insensitive).
These flags are guardrails for the agent, not access control. They decide which Teamarr
endpoints become MCP tools; Teamarr's API itself has no authentication, so anything that can
reach TEAMARR_URL can still call every endpoint directly. Protect the Teamarr port with your
network, not with this server.
Note also that Teamarr's own channel tools do reach into Dispatcharr: delete_managed_channel
removes a Teamarr-managed channel from Dispatcharr. The line drawn here is Teamarr-managed
channels only; DELETE /api/v1/channels/dispatcharr/{id}, which can remove any Dispatcharr
channel by id, is never exposed.
By default the server hides anything that deletes or resets data: every DELETE, both backup
restore endpoints, templates/restore-defaults, channels/reset, the match-cache and
game-data-cache clears, and clearing run history. Set TEAMARR_MCP_ENABLE_DESTRUCTIVE=true to
expose them; their descriptions are prefixed [destructive].
TEAMARR_MCP_READ_ONLY=true gives you a browse-only server with GET tools only.
Some routes are never exposed: the support bundle and backup downloads (binary), the XMLTV
outputs (large documents), the SSE generation log stream, the raw whole-block settings PUTs
(use update_settings), DELETE /api/v1/backup/{filename} (unrecoverable), and
DELETE /api/v1/channels/dispatcharr/{id}, which deletes a channel inside Dispatcharr itself
rather than a Teamarr-managed one. Block anything else with
TEAMARR_MCP_EXCLUDE_PATHS.
Tool names come from Teamarr's FastAPI route names with the auto-generated path suffix removed,
so list_teams_api_v1_teams_get becomes list_teams. Collisions are resolved explicitly
(patch_team vs update_team, create_detection_keyword vs create_keyword).
Against Teamarr 2.17.0 (225 API operations):
| Mode | Tools |
|---|---|
| default | 180 (174 generated + 6 curated) |
TEAMARR_MCP_READ_ONLY=true |
110 |
TEAMARR_MCP_ENABLE_DESTRUCTIVE=true |
208 |
Run teamarr_info to see the live numbers for your instance.
- Dispatcharr. Teamarr renumbers event channels constantly (
channel_stability_mode, daily reset). Point Dispatcharr consumers at M3U/EPG URLs with?tvg_id_source=tvg_idso they key on the stableteamarr-event-<id>ids instead of channel numbers. - Podium. Stream-ordering rules can consume
stream_statspublished by Podium. Teamarr rewrites the stream order every run, so do not hand-order streams in Dispatcharr. - Matchup art. Optional artwork via
epg.art_base_urlpointing at a game-thumbs instance. - TheSportsDB. Since Teamarr 2.16 TheSportsDB works only with a premium key. Leagues with
provider: tsdband all custom leagues return no fixtures without one. Usecheck_tsdb_gated_subscriptions, thenupdate_settings(block="display", changes={"tsdb_api_key": "..."}). - Known upstream cosmetic bug. A postgame title can read " Complete" while the description says the event "has not yet ended" when the provider returns no final status.
uv sync --extra dev
uv run pytest # unit tests, mocked Teamarr
uv run ruff check .
TEAMARR_TEST_URL=http://192.168.1.x:9195 uv run pytest tests/live # read-only live checks
uv run python scripts/refresh_spec.py http://192.168.1.x:9195 # refresh the vendored specThe update_settings round trip was verified against a live Teamarr 2.17.0: a merged PUT of the
lifecycle block left every field, including channel_range_start, unchanged.
Every push to main publishes a multi-arch image to GHCR as :latest and :main. Releases are
tagged v*, which adds version tags and publishes the package to PyPI via trusted publishing, which requires the GitHub publisher to be registered on pypi.org for the
teamarr-mcp project (environment pypi) before the first tag.
MIT