A professional-grade web interface for managing IPTV configurations with Dispatcharr. Built with React + TypeScript and Python FastAPI.
ECM gives you full control over your IPTV setup: manage M3U accounts and EPG sources, create and organize channels with drag-and-drop, automate channel creation with a powerful rules engine, probe stream health, and monitor live streaming stats — all from a single interface.
services:
ecm:
image: ghcr.io/motwakorb/enhancedchannelmanager:latest
ports:
- "6100:6100" # HTTP (configurable via ECM_PORT)
- "6143:6143" # HTTPS (configurable via ECM_HTTPS_PORT)
volumes:
- ./config:/config
environment:
- PUID=1000
- PGID=1000
- ECM_PORT=6100
- ECM_HTTPS_PORT=6143
# The image ships a HEALTHCHECK with `start_period=120s` baked in
# (bd-ul0ah). On long-running installs the first-run migrations can
# run against a bloated SQLite WAL file and take >30s — Docker's
# default start_period would mark the container unhealthy before
# migrations finish. If you need to override, set the healthcheck
# block here; operators on consistently fast installs can lower it.That's it. Open http://localhost:6100 and the setup wizard will guide you through creating an admin account and connecting to Dispatcharr.
To add the optional MCP server for managing ECM through Claude, add the MCP service to your compose file:
services:
ecm:
image: ghcr.io/motwakorb/enhancedchannelmanager:latest
ports:
- "6100:6100"
- "6143:6143"
volumes:
- ./config:/config
# ECM writes MCP credential material here. This is the ONLY ECM-owned
# directory the AI-facing sidecar can see.
- ecm-mcp-secrets:/run/secrets/ecm-mcp
environment:
- PUID=1000
- PGID=1000
- MCP_SECRETS_DIR=/run/secrets/ecm-mcp
ecm-mcp:
image: ghcr.io/motwakorb/enhancedchannelmanager-mcp:latest
ports:
- "127.0.0.1:6101:6101"
volumes:
# Credential projection only. Do NOT mount /config here: the sidecar is
# the process most exposed to prompt injection, and a /config mount puts
# settings.json, auth_settings.json, the audit journal, TLS private keys
# and every stored backup one file read away from it.
- ecm-mcp-secrets:/run/secrets/ecm-mcp:ro
# Must match ECM's PUID/PGID above: the projection is owner-only (0600) and
# the sidecar can read it only because it runs as the same account.
user: "1000:1000"
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp:rw,noexec,nosuid,nodev,size=16m,mode=1777
pids_limit: 128
mem_limit: 256m
cpus: 1.0
environment:
- MCP_SECRETS_DIR=/run/secrets/ecm-mcp
- ECM_URL=http://ecm:6100
- MCP_PORT=6101
# Container-internal bind; host publishing above remains loopback-only.
- MCP_BIND_ADDRESS=0.0.0.0
depends_on:
ecm:
condition: service_healthy
volumes:
ecm-mcp-secrets:Upgrading from an MCP sidecar that mounted ./config:/config:ro. Add the
ecm-mcp-secrets volume and the MCP_SECRETS_DIR variable to both services
as shown, then recreate. Recreating the containers alone is not enough. The
sidecar images from v0.18.1 onward default MCP_SECRETS_DIR to
/run/secrets/ecm-mcp, so without that volume the sidecar finds an empty
directory and reports api_key_status: file_not_found permanently. Once the
volume is in place, ECM publishes an existing key during startup or provisions
one when upgrading settings that predate the field; no save or regeneration is
needed to make the sidecar ready. An explicitly revoked key stays revoked.
Or if you're building from source, use the MCP compose overlay:
docker compose -f docker-compose.yml -f docker-compose.mcp.yml up -dThis default publishes MCP on host loopback only. For another machine, put an HTTPS reverse proxy in front of MCP and use the fail-closed remote overlay:
MCP_ALLOWED_HOSTS=mcp.example.home \
MCP_TRUSTED_PROXY_IPS=172.20.0.10 \
docker compose -f docker-compose.yml -f docker-compose.mcp.yml \
-f docker-compose.mcp.remote.yml up -dThe proxy must terminate TLS and forward to port 6101. Do not expose port 6101
through a router or firewall; remote mode rejects non-HTTPS /mcp requests.
ECM's own TLS setting does not protect MCP. It terminates HTTPS for ECM's
web interface on port 6143 only. The MCP sidecar is a separate listener with no
TLS of its own, so the reverse proxy is what encrypts MCP traffic.
MCP_TRUSTED_PROXY_IPS accepts only explicit IP addresses or bounded CIDRs.
Trust-all values (*, 0.0.0.0/0, and ::/0) and malformed entries stop the
sidecar at startup. Forwarded HTTPS is honored only from a configured peer.
Reaching the MCP container from ECM — ECM's Settings > MCP Integration status badge probes the MCP server's /health endpoint. By default it targets ecm-mcp:6101, which Docker DNS resolves to the MCP container on the canonical compose network — no extra configuration needed. If you run both containers with network_mode: host (host network namespace shared), set MCP_HOST=localhost on the ECM service so the probe targets the host loopback instead of the (non-existent on that topology) ecm-mcp DNS name.
Allowed MCP hostnames: The MCP sidecar accepts localhost, loopback IPs,
and the Compose service name ecm-mcp by default. The remote HTTPS profile
requires the proxy-facing hostname in MCP_ALLOWED_HOSTS (comma-separated for
more than one), then recreates the container.
List hostnames/IPs only: no scheme, port, path, or wildcard. Requests carrying
any other or malformed Host value are rejected before MCP routing.
Reaching ECM from the MCP container — the symmetric case. The MCP server calls ECM's backend API at its ECM_URL (default http://ecm:6100, Docker DNS on the canonical compose network). If both containers run with network_mode: host, the ecm service name has no DNS entry on the shared host network and the backend answers only on the host loopback, so every MCP tool call fails with All connection attempts failed. Fix: set ECM_URL=http://localhost:6100 on the MCP service. See MCP integration troubleshooting.
Upgrade note (v0.17.1-0066+): If you previously ran ECM and ecm-mcp with
network_mode: hostand never setMCP_HOST, you need to act. Earlier versions hardcodedlocalhostas the probe target; v0.17.1-0064 changed the default toecm-mcp(the canonical compose service name). On a host-networking deploy,ecm-mcpdoes not resolve — so after pullingdev, your Settings > MCP Integration badge will show "MCP server not reachable" even when MCP is healthy. Fix: addMCP_HOST=localhostto the ECM service'senvironmentblock in your compose file and restart the container.
See MCP Server (Claude Integration) for setup instructions.
User / Group Identifiers:
- PUID (default: 1000) — User ID the application runs as
- PGID (default: 1000) — Group ID the application runs as
Set these to match the owner of your bind-mounted volumes to avoid permission issues. Find your IDs with id your_user.
Port Configuration:
- ECM_PORT (default: 6100): HTTP interface. It remains available when TLS is enabled, but ECM refuses to start or refresh a browser session there (see below).
- ECM_HTTPS_PORT (default: 6143) — HTTPS interface (when TLS is configured in Settings)
When ECM TLS is enabled, use the HTTPS address for the web UI. The HTTP port
continues to serve health checks and unauthenticated recovery surfaces, but sign-in,
Dispatcharr sign-in and token refresh all answer 403 there with a message naming the
HTTPS address, rather than appearing to succeed and then failing on the next request.
Session cookies are Secure, HttpOnly and SameSite=Lax.
Turning TLS on signs everyone out once. The moment ECM starts terminating TLS, every existing browser session is revoked, so a browser still holding a pre-activation cookie cannot keep replaying it. Everyone, including the administrator who made the change, signs in again over HTTPS. That moment is wherever it actually happens: switching TLS on when a certificate is already present, issuing or completing a Let's Encrypt certificate, uploading one manually, or a manual renewal that issues the first certificate. Switching TLS on before any certificate exists is not activation and signs nobody out, because you still need the UI to finish issuing.
Reverse proxy deployments should set the canonical public_base_url to an https://
origin. ECM's own policy code does not consult X-Forwarded-Proto, X-Forwarded-Host
or Forwarded, because it has no trusted-proxy allowlist of its own. That setting
is how a proxy declares its external scheme. (Note that uvicorn, the server ECM runs under,
enables its own ProxyHeadersMiddleware by default and may rewrite the request scheme
for clients within FORWARDED_ALLOW_IPS, which defaults to 127.0.0.1. That is
uvicorn's policy, not ECM's.)
There are two escape hatches. Both send session credentials in plaintext, so use them
only on a trusted network and close them the moment HTTPS is repaired. Both work
regardless of whether public_base_url is set.
- If you can still sign in over HTTPS, enable Emergency recovery: allow
authenticated sessions over HTTP in Settings › TLS. While it is actually costing you
protection (ECM's own TLS is on, or
public_base_urlis anhttps://origin), the TLS panel shows a plaintext-session warning banner and ECM logs a warning. On a plain-HTTP install with neither, the hatch changes nothing and both stay quiet. - If you cannot sign in at all, stop ECM, set
ECM_ALLOW_HTTP_SESSION_COOKIES=trueon the ECM container, and restart it. ECM logs a warning at startup for as long as it is set. Sign in through HTTP only long enough to repair or disable TLS, then remove the variable (or set it back tofalse) and restart ECM.
Values other than 1, true, yes or on (including the false that
docker-compose.yml ships by default) leave the protection ON.
Your browser may refuse to open the HTTP port at all. Read this before you need it.
Every HTTPS response from ECM carries Strict-Transport-Security: max-age=31536000
(one year, deliberately without includeSubDomains and without preload). Per
RFC 6797 §8.3 that pin is scoped
to the hostname and is port-agnostic: once you have visited
https://ecm.example.com:6143 even once, your browser will silently upgrade
http://ecm.example.com:6100 to https://ecm.example.com:6100 for a year, with no
click-through to bypass it. So "browse to http://ecm.example.com:6100" is not a
recovery instruction that works on a pinned browser. Use one of these instead:
- Reach ECM by IP literal, e.g.
http://192.168.1.50:6100. HSTS pins are stored per hostname and are never applied to IP addresses, so this always works and is the fastest route. - Use a different hostname for the same instance (another DNS name, or a
hosts-file alias) that you have never visited over HTTPS. - Clear the pin for the hostname: in Chrome/Edge open
chrome://net-internals/#hsts, enter the domain under Delete domain security policies, and delete it. In Firefox, open the History sidebar (or Library › History), right-click the site and choose Forget About This Site. Note that this also clears that site's cookies, cache and history. Then browse to the HTTP port.
Volumes:
/config— Persistent storage for database, settings, logos, TLS certificates, and backups
ECM supports full backup and restore of all configuration. You can create backups from Settings, or restore from a backup during the first-run setup wizard.
# Frontend
cd frontend && npm install && npm run dev
# Backend
cd backend && pip install -r requirements.txt && uvicorn main:app --reloadFull CRUD for channels and groups with a split-pane layout. Drag-and-drop streams onto channels, reorder streams by priority, bulk-create channels from stream groups with smart name normalization (quality variants, country prefixes, timezone handling), and organize everything into numbered channel groups. Staged edit mode lets you queue changes locally and commit or discard them as a batch.
Manage Standard M3U, XtreamCodes, and HD Homerun accounts. Link related accounts so group enable/disable changes cascade automatically. Track changes detected across M3U refreshes with filtering, search, and optional email digest notifications.
Configure multiple XMLTV and Schedules Direct EPG sources with drag-and-drop priority ordering. Create dummy EPG entries for channels without guide data. Bulk EPG assignment uses country-aware matching, call sign scoring, and HD preference to automatically map EPG data to channels.
EPG grid view with now-playing highlights, date/time navigation, channel profile filtering, and click-to-edit channel metadata.
A rules-based automation engine for channel creation, stream merging, and lifecycle management. Build complex conditions (stream name, group, quality, codec, normalized matching, etc.) with AND/OR logic, then define actions (create channel/group, merge streams, assign metadata, set variables, name transforms). Per-rule normalization group selection lets each rule apply specific normalization groups. Supports dry-run preview, execution rollback, YAML import/export, orphan reconciliation, and a diagnostic debug bundle for troubleshooting.
Automated stream probing with configurable schedules, batch sizes, retry logic, and rate limit detection. Profile-aware probing distributes connections across M3U profiles. Results drive smart stream sorting by resolution, bitrate, framerate, video codec, and M3U priority with configurable ordering for deprioritized stream categories. Black screen detection identifies streams showing dark/blank content, and low FPS detection flags streams below a configurable threshold (5/10/15/20 FPS). Both are deprioritized in Smart Sort. A strikeout system tracks consecutive failures for bulk cleanup.
Browse, search, upload, and assign logos to channels. Supports URL import and file upload to Dispatcharr with usage tracking and pagination.
Live dashboard showing active channels, M3U connection counts, per-channel FFmpeg metrics (speed, FPS, bitrate), and bandwidth charts. Enhanced analytics include unique viewer tracking with Dispatcharr user identification, per-channel bandwidth, popularity scoring with trend analysis, and watch history with user attribution.
Activity log tracking all changes to channels, EPG, and M3U accounts with filtering by category, action type, and time range. A daily Journal Noise Purge task auto-deletes automated-noise entries (watch start/stop telemetry and automated Channel Pipeline rule create/delete churn) older than a configurable retention window (default 3 days); operator-initiated entries and all other categories are kept.
Comprehensive configuration including Dispatcharr connection, channel defaults, stream name normalization (tag-based and rule-based engines), stream probing, scheduled tasks (EPG/M3U refresh, probing, cleanup), alert methods (Discord, Telegram, email), authentication (local + Dispatcharr SSO), user management, TLS certificates, VLC integration, appearance themes, and backup/restore.
First-run setup wizard, local auth with bcrypt hashing, Dispatcharr SSO, account linking, email-based password reset, and CLI password reset for lockout recovery. JWT-based sessions with automatic token refresh.
In-app notification bell with history, active task pinning, and external alert methods (Discord webhooks, Telegram bots, SMTP email) with digest batching and source filtering.
ECM includes an MCP (Model Context Protocol) server: an optional sidecar container that exposes ECM's functionality to Claude — Claude Desktop, Claude Code, or any MCP-capable client — so you can manage your install in plain language instead of clicking through the UI. 130 tools across 14 domains (channels, channel groups, streams, M3U accounts, EPG sources, channel pipeline, scheduled tasks, stats, system/backup, notifications, profiles, normalization, deduplication, Emby integration), plus an overview resource that gives Claude a one-shot snapshot of your install.
Everything Claude does runs against your live ECM through the API — it's the same operations the UI performs, just driven by conversation. Mutating actions report the resulting state back (e.g. the new channel's group and number) so you can confirm the change took effect.
Things you can ask Claude to do:
Channels & streams
- "List every channel in the Sports group that has fewer than 2 streams"
- "Find duplicate channels and merge each set, keeping the one with the most streams"
- "Add these 8 streams to channel 412 and put the 1080p ones first"
- "Renumber the News group starting at 200"
- "Build a channel lineup from this list of names and fuzzy-match streams to each"
Channel Pipeline
- "Run the Channel Pipeline and tell me what it created"
- "Analyze my Channel Pipeline rules and flag anything misconfigured" — the Rule Analyzer catches regex/structural mistakes (
UK|that matches everything,^4Ktyped under Contains that matches nothing, double-escape typos, OR-arms missing a group guard, merges into empty groups) without running the rule - "Create a rule that auto-creates channels for any stream whose name contains 'PPV', into the Events group"
- "Why didn't the 4K Sports rule match anything?" — Claude can pull a debug bundle and analyze it
- "Clear all the channels that the Channel Pipeline made in the Test group"
M3U & EPG
- "Refresh all my M3U accounts and report any failures"
- "Which channels are missing an EPG ID?" → "assign tvg_ids to those"
- "Probe every stream in the Movies group and list the dead ones"
Housekeeping & insight
- "Show me this week's watch stats" / "which channels has nobody watched in 30 days?"
- "What scheduled tasks are enabled?" / "run the cleanup task"
- "Back up my config" / "give me an overview of this ECM install"
Dispatcharr fields in
settings.json(GH #273)
Field in settings.jsonWhat it is for urlDispatcharr base URL dispatcharr_api_keyDispatcharr REST API token — ECM uses this to talk to Dispatcharr. (Canonical field name as of v0.17.1, GH #273. Operators upgrading from v0.17.0 or earlier will have the value in the legacy api_keyfield; ECM auto-migrates on next startup with a one-time[CONFIG] Reading deprecated 'api_key' field …WARN log.) Never replace it with an MCP key.api_keyDEPRECATED legacy alias for dispatcharr_api_key. ECM still reads this for one release of back-compat (v0.17.x). The first read after upgrade emits a deprecation WARN and mirrors the value intodispatcharr_api_keyon the next save.MCP key authority (separate from Dispatcharr settings)
Generate, regenerate, or revoke the MCP client key only through Settings > MCP Integration. Copy the generated or regenerated key from the UI into each client. The owner-only
/run/secrets/ecm-mcp/api-keyprojection is the sole live authority for inbound MCP authentication. Thesettings.json:mcp_api_keyfield is only a compatibility mirror; generic settings saves and manual changes cannot promote it over the authority and instead repair it from the validated projection. An explicitly empty authority is durable revocation.The separate
mcp-service.jsonprojection contains the sidecar's private backend and confirmation credentials. Operators never copy those credentials into a client..api-key.recoveryis an internal redo record for interrupted rotation or revocation; do not edit or delete it as cleanup.Do not put the MCP key in
dispatcharr_api_key(or its legacyapi_keyalias): doing so breaks every channel and stream operation because Dispatcharr rejects the wrong credential. If/healthreportsapi_key_configured: false, its status distinguishes an absent (file_not_found), blank (field_empty), or malformed/unreadable (invalid_key) projection. Read it withGET http://localhost:6101/healthon the Docker host.Migration example. A v0.17.0
settings.jsonfrom an operator hit by GH #273:{ "url": "http://dispatcharr:9191", "api_key": "REDACTED_DISPATCHARR_REST_TOKEN" }After the first v0.17.1 startup and the next settings save, the file becomes:
{ "url": "http://dispatcharr:9191", "dispatcharr_api_key": "REDACTED_DISPATCHARR_REST_TOKEN", "api_key": "REDACTED_DISPATCHARR_REST_TOKEN" }Both fields hold the same Dispatcharr token. The duplicate is intentional so external scripts that still read
api_keykeep working. ECM handles the migration and compatibility writes; neither field is an MCP credential.
- Generate an API key in ECM Settings > MCP Integration and copy the displayed key for your MCP client. ECM publishes the authoritative projection and compatibility mirror automatically.
- Start the MCP container — add the
ecm-mcpservice to your compose file (see With MCP Server) and start it on port 6101 - Connect Claude — choose your method:
ECM's MCP server is authenticated with the public MCP client key in an Authorization: Bearer header. The default deployment is available only on the Docker host's loopback interface. Use the HTTPS remote profile above for access from another machine.
| Method | Node.js? | Best for |
|---|---|---|
| Claude Desktop — mcp-remote bridge | Yes (LTS 18+ on the Claude Desktop machine) | Claude Desktop users; private/homelab deploys; existing setups |
Claude Code — .mcp.json |
No | Claude Code in any project; direct HTTP, no Node.js |
✅ Works on a private network — no public exposure. mcp-remote runs on your machine and connects to ECM over your LAN/VPN, so ECM never has to be reachable from the internet (the cost is needing Node.js on the Claude Desktop machine).
Claude Desktop talks to remote MCP servers through the mcp-remote bridge. Add this to your claude_desktop_config.json:
Prerequisite: Claude Desktop does not bundle Node.js. The
mcp-remotebridge is an npm package that Claude Desktop runs vianpx, so you need Node.js installed on the same machine as Claude Desktop (any current LTS — Node 18+ — is fine). Install it from nodejs.org (or via a package manager:winget install OpenJS.NodeJS.LTSon Windows,brew install nodeon macOS,apt install nodejs npmon Debian/Ubuntu). Without Node on PATH, Claude Desktop fails to launch the MCP server with aspawn npx ENOENTerror in its logs.
{
"mcpServers": {
"ecm": {
"command": "npx",
"args": [
"mcp-remote",
"http://localhost:6101/mcp",
"--header",
"Authorization:${ECM_MCP_AUTH}",
"--allow-http"
]
}
}
}Set ECM_MCP_AUTH in the operating-system environment to Bearer <your key>
before starting Claude Desktop. --allow-http is appropriate only for this
loopback URL; use https:// and omit it for remote access.
Note: the Bearer value is the MCP key copied from Settings > MCP Integration, not the Dispatcharr REST token in
dispatcharr_api_keyor its legacyapi_keyalias.
✅ Works on a private network — no Node.js, no public exposure. Claude Code speaks the HTTP transport natively and connects directly from your machine, so a LAN/VPN-reachable ECM is all you need. This is the simplest private path if you use Claude Code.
Create a .mcp.json file in any project directory where you want ECM tools available:
{
"mcpServers": {
"ecm": {
"type": "http",
"url": "http://localhost:6101/mcp",
"headers": {
"Authorization": "Bearer ${ECM_MCP_API_KEY}"
}
}
}
}To connect:
- Set
ECM_MCP_API_KEYin your local environment and create the.mcp.jsonabove - Start Claude Code in that directory — it auto-detects
.mcp.jsonon launch - Run
/mcpto reconnect if the MCP server restarts - Ask Claude to manage your channels — e.g. "list my channels", "create a Channel Pipeline rule for sports", "probe all streams"
If running ECM locally, use localhost as your host. If the MCP container is on the same Docker network as Claude Code, use the container name (ecm-mcp).
Upgrading from an earlier version: remove ?api_key=... from every MCP URL and configure the Bearer header shown above. The deprecated SSE endpoints remain removed. Rotate the old key through Settings > MCP Integration after removing URL-based configs because URLs may have been retained in logs or histories.
Redeploying or rotating the MCP key: use Settings > MCP Integration > Regenerate Key, copy the returned key, then update the local environment variable used for the Bearer header. Rotation is effective on the next request without restarting the sidecar. Revoke access with Settings > MCP Integration > Revoke Key. Do not edit server-side credential projection files or dispatcharr_api_key (or its legacy api_key alias).
For the full reference — step-by-step connection setup, key rotation details, and troubleshooting — see docs/user_guide/integrations/mcp.md.
| Tool | Description |
|---|---|
| Channels (20) | |
list_channels |
List channels with optional group/search/stream count filtering |
get_channel |
Get detailed channel info (streams, EPG, logo) |
create_channel |
Create a new channel |
update_channel |
Update channel name, number, or group |
delete_channel |
Delete a channel |
bulk_delete_channels |
Delete multiple channels at once |
add_stream_to_channel |
Add a stream to a channel |
add_stream |
Create a channel from a stream name and assign it to a group, with deduplication control |
remove_stream_from_channel |
Remove a stream from a channel |
reorder_streams |
Reorder streams within a channel by priority |
assign_channel_numbers |
Bulk-assign sequential channel numbers |
merge_channels |
Merge multiple channels into one |
find_duplicate_channels |
Scan for channels with matching normalized names |
bulk_merge_duplicate_channels |
Merge multiple groups of duplicates at once |
bulk_commit_channels |
Commit a batch of channel operations atomically |
build_channel_lineup |
Bulk-create channels and fuzzy-match streams |
clear_auto_created |
Remove auto-created channels by group |
bulk_add_streams_to_channel |
Add multiple streams to a channel in one backend call (single Dispatcharr roundtrip) |
bulk_assign_epg |
Assign EPG IDs (tvg_id) to multiple channels |
set_logo_from_epg |
Set channel logos from their linked EPG entry's icon_url |
| Groups (8) | |
list_channel_groups |
List all groups with channel counts |
create_channel_group |
Create a new group |
get_orphaned_groups |
Find groups with no channels |
delete_channel_group |
Delete a group, optionally deleting its channels |
delete_orphaned_groups |
Delete groups with no channels assigned |
get_hidden_groups |
List hidden channel groups |
get_auto_created_groups |
List auto-created groups |
get_groups_with_streams |
List groups with stream count info |
| Streams (18) | |
list_streams |
List streams with group/provider/search filtering |
search_streams |
Search streams by name across all providers |
get_streams_by_ids |
Fetch detailed info for specific stream IDs |
get_streams_for_channel |
Get streams assigned to a channel |
get_stream_health |
Stream health summary from last probe |
probe_streams |
Start probing all streams (background) |
probe_single_stream |
Probe one specific stream |
probe_bulk_streams |
Probe multiple streams at once |
get_probe_progress |
Check ongoing probe status |
get_probe_results |
Results from the most recent probe |
get_struck_out_streams |
List streams with consecutive failures |
cleanup_struck_out_streams |
Remove struck-out streams from channels |
bulk_remove_streams |
Remove multiple streams from a channel |
cancel_probe |
Cancel a running probe |
bulk_search_streams |
Search multiple stream names in one call |
fuzzy_match_stream |
Find best fuzzy match for a stream name |
preview_fuzzy_matches |
Preview scored stream→channel fuzzy matches without writing anything |
match_streams_to_channels |
Match streams to channels by name similarity |
| M3U (9) | |
list_m3u_accounts |
List all M3U provider accounts |
get_m3u_account |
Get detailed account info |
create_m3u_account |
Create a new M3U account |
update_m3u_account |
Update account name or URL |
delete_m3u_account |
Delete an M3U account |
refresh_m3u |
Refresh a specific M3U account |
refresh_all_m3u |
Refresh all M3U accounts |
update_m3u_group_settings |
Enable/disable stream groups on an account |
bulk_update_m3u_group_settings |
Enable/disable multiple stream groups at once |
| EPG (15) | |
list_epg_sources |
List EPG data sources |
create_epg_source |
Create a new EPG source |
update_epg_source |
Update an EPG source |
delete_epg_source |
Delete an EPG source |
refresh_epg |
Refresh a specific EPG source |
match_channels_epg |
Auto-match channels to EPG data |
link_channel_epg |
Link a channel to a chosen EPG candidate so its guide data attaches |
refresh_all_epg |
Refresh multiple or all EPG sources at once |
get_epg_grid |
What's on TV now — EPG schedule grid |
list_sd_lineups |
List the active Schedules Direct lineups for an SD EPG source |
search_sd_lineups |
Search Schedules Direct headends/lineups by country + postal code |
add_sd_lineup |
Add a Schedules Direct lineup to the account (rate-limited) |
remove_sd_lineup |
Remove a Schedules Direct lineup from the account |
list_dummy_epg_profiles |
List dummy EPG profiles |
generate_dummy_epg |
Regenerate dummy EPG XMLTV data |
| Channel Pipeline (14) | |
Deprecated alias: every tool below is also callable under its old *_auto_creation_* name (e.g. run_auto_creation, list_auto_creation_rules). The alias forwards to the same handler and continues to work, but is hidden from new-tool listings — use the canonical channel_pipeline-named tools shown here. |
|
list_channel_pipeline_rules |
List all rules |
get_channel_pipeline_rule |
Get rule details (conditions, actions, normalization groups, sort config) |
create_channel_pipeline_rule |
Create a rule with conditions, actions, and per-rule normalization groups |
update_channel_pipeline_rule |
Update an existing rule (supports normalization_group_ids) |
delete_channel_pipeline_rule |
Delete a rule |
toggle_channel_pipeline_rule |
Enable/disable a rule |
duplicate_channel_pipeline_rule |
Duplicate a rule |
run_channel_pipeline |
Run pipeline (dry_run=true by default) |
list_channel_pipeline_executions |
View execution history |
rollback_channel_pipeline |
Undo an execution |
restore_channel_pipeline_snapshot |
Full whole-run revert of a Channel Pipeline run from its pre-run snapshot |
analyze_channel_pipeline_rules |
Lint and structurally analyze Channel Pipeline rules |
get_channel_pipeline_debug_bundle |
Info about the diagnostic debug bundle for troubleshooting |
bulk_toggle_channel_pipeline_rules |
Toggle multiple rules at once |
| Tasks (7) | |
list_tasks |
List scheduled tasks and status |
run_task |
Run a task immediately |
cancel_task |
Cancel a running task |
get_task_history |
View task execution history |
list_task_schedules |
List schedules for a task |
create_task_schedule |
Create a schedule for a task (interval / daily / weekly / biweekly / monthly) |
delete_task_schedule |
Delete a schedule |
| Stats (14) | |
get_channel_stats |
Channel viewing stats and active viewers |
get_top_watched |
Most-watched channels by viewing time |
get_bandwidth |
Bandwidth usage (today, week, month, all-time) |
get_channel_bandwidth |
Per-channel bandwidth statistics |
get_popularity_rankings |
Channel popularity scores and trending |
get_channel_popularity |
Popularity score and metrics for a specific channel |
get_trending |
Channels trending up or down in popularity |
get_watch_history |
Watch history with user attribution and filters (channel, IP, days) |
get_unique_viewers |
Unique viewer counts by channel |
get_user_watch_time |
Per-user watch-time totals |
get_user_channel_breakdown |
Per-channel watch-time breakdown for a specific user |
get_provider_stats |
Per-provider statistics |
get_activity |
Recent system activity (channel start/stop, buffering, client connections) |
compute_stream_sort |
Compute optimal stream sort order (resolution, bitrate, video codec, etc.) |
| System (10) | |
get_settings |
ECM settings overview |
create_backup |
Create config backup |
restore_backup |
Restore ECM configuration from a saved backup ZIP on the server |
create_dbas_backup |
Create a DBAS backup artifact (ecm-backup-.zip) on the server |
restore_dbas_backup_saved |
Restore from a saved DBAS artifact on the server, by filename |
get_export_sections |
List available YAML export sections |
list_saved_backups |
List saved backup files |
delete_saved_backup |
Delete a saved backup file |
get_journal |
Activity audit log (with limit/category filters) |
list_cloud_targets |
List configured cloud storage targets (DBAS backup upload destinations) |
| Notifications (5) | |
list_notifications |
List notifications with unread count |
mark_notifications_read |
Mark all as read |
delete_all_notifications |
Clear all notifications |
list_alert_methods |
List configured alert methods (Discord, Telegram, email) |
test_alert_method |
Send a test notification through an alert method. Not usable over MCP (build 0089): the endpoint refuses the MCP service key with 403, by design. An admin runs the test from Settings > Alert Methods |
| Profiles (3) | |
list_channel_profiles |
List channel profiles |
list_stream_profiles |
List stream profiles |
apply_profile_to_channels |
Bulk-assign a profile to channels |
| Normalization (3) | |
test_normalization |
Test how stream names normalize |
list_normalization_rules |
List normalization rule groups |
set_normalization_group_enabled |
Enable or disable a normalization rule group globally |
| Dedup (3) | |
list_pending_channel_merges |
List pending channel-merge candidates from the dedup queue |
accept_channel_merge |
Accept a pending channel merge — triggers the Dispatcharr merge |
dismiss_channel_merge |
Dismiss a pending channel merge — rejects the dedup candidate |
| Emby (1) | |
clear_emby_logos |
Clear cached Emby channel logos so Emby re-fetches fresh ones |
Three read-only MCP resources provide quick context without a tool call: ecm://stats/overview, ecm://channels/summary, and ecm://tasks/status.
# Interactive mode (lists users, prompts for password)
docker exec -it enhancedchannelmanager python /app/reset_password.py
# Non-interactive
docker exec enhancedchannelmanager python /app/reset_password.py -u admin -p 'NewPass123'
# Skip password strength validation
docker exec enhancedchannelmanager python /app/reset_password.py -u admin -p 'simple' --force./scripts/search-stream.sh http://dispatcharr:9191 admin password "ESPN"| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript, Vite, @dnd-kit |
| Backend | Python, FastAPI, 20+ modular API routers |
| MCP Server | Python, FastMCP, Streamable HTTP transport, 130 tools |
| Deployment | Docker Compose, two containers (ECM + MCP) |
Interactive API docs are available at /api/docs (Swagger UI) and /api/redoc. See docs/api.md for the full endpoint reference.
- v0.17.2 — Full Stats v2 MCP coverage (8 new tools: provider stats, per-user watch time, trending, activity, channel bandwidth; media-server attribution now queryable via Claude); 30+ MCP correctness fixes from a live sweep of all tools; MCP API-key timing-attack hardening
- v0.17.1 — Plex + Jellyfin user attribution and multi-viewer display; real client IP threading through the attribution pipeline; SSRF hardening on media-server test-connection endpoints
- v0.17.0 — Stats v2 foundation:
session_telemetrytable, per-user watch-time API, Alembic schema migration system, Prometheus/metrics, structured JSON logging with trace IDs; ghost-source-channel fix after merge in edit mode - v0.16.0 — MCP server for natural language channel management via Claude (124 tools, 14 domains, Streamable HTTP transport); auto-creation rule analyzer; Alembic migrations; structured logging; per-rule normalization and sort options (an earlier 0.16.0 build was rolled back on 2026-04-20 before any external consumer pulled it; this is the shipping release, cut 2026-05-12)
- v0.15.1 — OWASP hardening (security headers, CORS, rate limiting, NIST password policy, log redaction, path validation)
- v0.15.0 — Server-side EPG matching, stream normalization, PUID/PGID support, low FPS detection, export/publish pipeline
- v0.14.0 — Dummy EPG profiles, auto-creation pipeline, normalization engine
- v0.13.0 — Backend modularization (20+ routers), auth system, task engine
See CHANGELOG.md [Unreleased] for the canonical list of fixes and features queued for the next cut.