An AI agent for your Plex media server. Ask it "why isn't the new episode of X downloading?" in Discord or from a chat widget inside Overseerr — it inspects Sonarr/Radarr queues, diagnoses stuck downloads, blocklists dead releases, triggers re-searches, and answers library questions, autonomously, with admin approval gates on destructive actions.
Built on the Claude Code CLI as the reasoning engine and FastMCP for the tool layer —
each chat message becomes a claude -p subprocess call wired to a purpose-built MCP server.
User message (Discord #channel or Overseerr widget)
→ bot.py (discord.py) or web.py (aiohttp POST /api/chat)
→ claude -p subprocess (--mcp-config mcp.json, per-user session ID)
→ MCP server (server.py, FastMCP)
→ Sonarr · Radarr · Plex · ffprobe · memory · Discord REST
→ response text
→ chunked Discord reply / JSON to widget
- Diagnose downloads: reads Sonarr/Radarr queues with error details, recognizes failure patterns (e.g. a debrid provider rejecting a release), clears + blocklists + re-searches without being told the steps.
- Answer library questions: what's on the server, resolution/codec via ffprobe, what's coming.
- Take requests conversationally alongside Overseerr.
- Remember context per user across conversations (optional Supermemory integration).
- Ask permission first: destructive actions post an approval embed to an admin channel and wait for a ✅/❌ reaction (5-minute timeout). On the web surface, an HMAC-verified identity token lets the admin's own requests auto-approve.
| Surface | Entry point | Auth |
|---|---|---|
| Discord bot | bot.py, replies in one configured channel |
Discord identity |
| Web widget | static/chat.html in an iframe (e.g. floating button injected into Overseerr via static/widget-inject.js) |
HMAC-signed identity token minted by a small reverse-proxy worker that verifies the Overseerr session — see PLEXBOT_AUTH_SECRET in .env.example |
The widget is iframe-only by design: chat.js refuses to render unless embedded by an
origin in ALLOWED_PARENT_ORIGINS (edit that list at the top of static/chat.js).
cp .env.example .env # fill in Discord/Sonarr/Radarr/Plex credentials
docker compose up -d --build
docker exec -it plex-agent claude login # one-time Claude auth inside the containerThe container bundles Node 22 + Python + ffmpeg + the Claude Code CLI and runs as a
non-root user. Web API listens on :8080 inside the container.
Running outside Docker:
pip install -e . && python -m plex_agent.botAll config is env-driven — see .env.example. Required: Discord bot
token + channel IDs, Sonarr/Radarr/Plex URLs + API keys. Optional: Supermemory (per-user
long-term memory), PLEXBOT_AUTH_SECRET (web identity), WEB_ADMIN_USER_ID.
Agent behavior lives in system-prompt.md — edit it to teach the
agent your stack's quirks (it ships with patterns for a Real-Debrid + SABnzbd setup as
a worked example). {{ADMIN_DISCORD_ID}} is substituted from the environment at runtime.
- Deterministic session per user (
uuid5of the user ID) so conversations resume;!resetstarts fresh. Web and Discord surfaces are namespaced separately. - Requester identity flows to the MCP server via env vars; admin approval is short-circuited only when the verified requester is the admin.
- A circuit breaker stops runaway Claude calls; replies are chunked to Discord's 2000-char limit.
docker compose restartdoes not reload.env— usedown && up.- The Claude Code CLI needs Node 22+ (empty output on Node 20).
--session-idmust be a valid UUID and only works for new sessions — resume existing ones with--resume.
MIT