Skip to content

tpisel/seedbox-mcp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Seedbox MCP

Two co-hosted services for a Plex + Radarr + Sonarr stack:

  • MCP server (seedbox-mcp) — exposes compact tools for Plex, Radarr, and Sonarr so your agent of choice can inspect media state and do basic actions.
  • Chat interface (seedbox-chat) — a Plex-authenticated single-page chat UI powered by Claude Haiku, pre-wired to the MCP server. Designed for family members who have Plex access but not Claude.

Runs anywhere Python can reach your Plex and *arr services — seedbox slots (Whatbox, Ultraseedbox, etc.), homelabs, NAS, VPS. The Whatbox-specific examples below cover one well-tested deployment path; adapt them to your environment (systemd, Docker, reverse proxy of choice).


MCP server

The service is intentionally narrow:

  • Plex is read-only.
  • Radarr and Sonarr writes are limited to add, re-search, queue actions, and exact-ID delete.
  • Write tools dry-run by default with confirm=false.
  • Delete tools preserve files by default with delete_files=false.
  • There are no torrent, indexer, filesystem, shell, scheduler, database, or frontend capabilities.

Local Setup

Requires just and uv.

cp .env.example .env
# fill in required values — see Configuration below
just setup
just run        # MCP server on :17432
just run-chat   # chat interface on :17433 (separate terminal)

The MCP endpoint is /mcp; a basic unauthenticated health check is available at /health.

Every /mcp request requires either a static bearer token or a valid OAuth access token:

Authorization: Bearer <MCP_BEARER_TOKEN>

Configuration

All settings are read from .env or environment variables.

Required

Variable Notes
MCP_BEARER_TOKEN Shared secret for bearer auth and the OAuth consent gate
MCP_PUBLIC_BASE_URL Public HTTPS base URL (e.g. https://mcp.example.box.ca) — required for OAuth discovery metadata
RADARR_URL Radarr base URL
RADARR_API_KEY Radarr → Settings → General → Security
RADARR_DEFAULT_ROOT_FOLDER Absolute path to movies root folder, as Radarr sees it
RADARR_DEFAULT_QUALITY_PROFILE_ID Integer — see Finding quality profile IDs
SONARR_URL Sonarr base URL
SONARR_API_KEY Sonarr → Settings → General → Security
SONARR_DEFAULT_ROOT_FOLDER Absolute path to TV root folder, as Sonarr sees it
SONARR_DEFAULT_QUALITY_PROFILE_ID Integer — see Finding quality profile IDs
PLEX_URL Plex base URL — must be reachable from where this server runs. On a seedbox slot use the public HTTPS URL; on the same host as Plex http://127.0.0.1:32400 is fine
PLEX_TOKEN Plex auth token — see Finding your Plex token

Optional

Variable Default Notes
MCP_HOST 127.0.0.1 Bind address
MCP_PORT 17432 Bind port
RADARR_DEFAULT_MIN_AVAILABILITY released announced, in_cinemas, or released
SONARR_DEFAULT_LANGUAGE_PROFILE_ID (none) Sonarr v3 language profile ID; omit for Sonarr v4+
SONARR_DEFAULT_SERIES_TYPE standard standard, daily, or anime
PLEX_VERIFY_TLS true Verify Plex HTTPS certificates. Set false only for a trusted endpoint with a self-signed certificate
PLEX_MOVIE_SECTION Movies Display name of the Plex movie library
PLEX_TV_SECTION TV Shows Display name of the Plex TV library
TAUTULLI_ENABLED false Set true to enable Tautulli watch-history enrichment
TAUTULLI_URL (none) Tautulli base URL
TAUTULLI_API_KEY (none) Tautulli API key
OAUTH_ACCESS_TOKEN_TTL 3600 OAuth access token lifetime in seconds

Startup logs print a redacted config summary. API keys, Plex tokens, bearer tokens, and request headers are never logged.


Chat Interface

A single-page chat UI backed by Claude Haiku. Anyone who has friend-level access to the Plex server can log in with their Plex account and chat with an assistant that has full access to the MCP tools.

Write actions (add, delete, queue) require an in-chat confirmation step — Haiku always calls tools with confirm=false first to show a preview, and only proceeds with confirm=true after the user says yes.

Configuration

The chat server reads the same .env file as the MCP server and adds:

Required

Variable Notes
CHAT_PUBLIC_BASE_URL Public HTTPS base URL for the chat app (e.g. https://chat.example.com) — used as the Plex OAuth callback origin
CHAT_SESSION_SECRET Long random string used to sign session cookies
CHAT_PLEX_CLIENT_ID Stable UUID identifying this app to Plex — generate once with python3 -c "import uuid; print(uuid.uuid4())"
ANTHROPIC_API_KEY Anthropic API key for Claude Haiku

Optional

Variable Default Notes
CHAT_HOST 127.0.0.1 Bind address
CHAT_PORT 17433 Bind port
SYSTEM_PROMPT_PATH (none) Path to a plain-text file that overrides the default system prompt

How authentication works

  1. User visits the chat URL and is redirected to plex.tv to sign in.
  2. After signing in, Plex redirects back to /auth/callback.
  3. The server verifies the user is a friend (shared-access user) of the Plex server.
  4. A signed session cookie is set — no expiry, valid until the browser clears cookies.

Only users who already have Plex friend access can log in. There is no separate user management.

Deploying the chat server

The chat server is launched alongside the MCP server by scripts/start.sh — see the Deployment section below. Front the chat port (17433) with HTTPS the same way you do for the MCP server, and set CHAT_PUBLIC_BASE_URL to the resulting public URL.

Finding your Plex token

Open Plex Web, play any item, then choose "Get Info" → "View XML". The X-Plex-Token query parameter in the URL is your token. Full guide: https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/

Finding quality profile IDs

The integer ID is visible in the URL when you click Edit on a profile in Settings → Profiles (e.g. /settings/profiles/edit/2 → ID is 2). You can also query the API:

# Radarr
curl -s "https://<radarr-host>/api/v3/qualityprofile?apikey=<key>" | python3 -m json.tool

# Sonarr
curl -s "https://<sonarr-host>/api/v3/qualityprofile?apikey=<key>" | python3 -m json.tool

Deployment (Whatbox example)

The pattern below is what the author runs on Whatbox. On a homelab with systemd, swap screen + @reboot for a systemd unit; on Docker, package the same python -m seedbox_mcp.server invocation. Whatever the runtime, the server expects to be fronted by HTTPS at MCP_PUBLIC_BASE_URL — on Whatbox that's a "managed HTTPS link"; elsewhere use Caddy, nginx, Tailscale Serve, or similar.

Clone and install

git clone <repo-url> ~/seedbox-mcp
cd ~/seedbox-mcp
cp .env.example .env
# fill in all required values
uv sync

Redeploying

Once it's cloned, push new code from your workstation with just deploy (or bash scripts/deploy.sh). It SSHes in, fast-forwards the checkout, runs uv sync, and restarts both servers. It expects a seedbox host in your ~/.ssh/config with key auth set up (ssh-copy-id seedbox); override the target/path/branch via SEEDBOX_SSH, SEEDBOX_DIR, SEEDBOX_BRANCH.

Run in the background with screen

scripts/start.sh kills any existing sessions and launches both servers in detached screen sessions (media-mcp and media-chat), tailing their output to mcp.log and chat.log:

bash scripts/start.sh

Reattach: screen -r media-mcp (or media-chat). Detach: Ctrl-A D.

Auto-restart on reboot

crontab -e

Add:

@reboot sleep 30 && bash ~/seedbox-mcp/scripts/start.sh

The sleep 30 gives the host time to bring up networking before the server tries to connect.

Restart the servers

bash scripts/start.sh

(The script kills the existing screen sessions before relaunching.)

Public HTTPS front-end

Front http://127.0.0.1:17432 (MCP) and http://127.0.0.1:17433 (chat) with HTTPS — Whatbox managed links, a reverse proxy (Caddy / nginx), Tailscale Serve, or equivalent — and set MCP_PUBLIC_BASE_URL and CHAT_PUBLIC_BASE_URL to the resulting URLs. OAuth discovery metadata is derived from these values, so they must match what your clients actually use.

Verify

curl https://<your-host>/health
curl https://<your-host>/.well-known/oauth-authorization-server

Connecting to Agents

Both Claude.ai and ChatGPT MCP connectors require OAuth 2.0. The server implements Authorization Code + PKCE automatically — no extra setup beyond a running server with MCP_PUBLIC_BASE_URL set.

Setup steps

  1. In the agent of your choice, add a new MCP integration:
    • MCP Server URL: https://<your-host>/mcp
    • Client ID: any string, e.g. claude-ai (the server accepts any value here)
    • Client Secret: leave blank or enter any dummy value — it is not validated (PKCE replaces client secrets)
  2. The agent will open https://<your-host>/oauth/authorize in your browser.
  3. Paste your MCP_BEARER_TOKEN into the consent form and submit.
  4. The agent stores the resulting access token (default 1-hour TTL) and refreshes it automatically via a 30-day refresh token.

Suggested model-facing instructions

Prefer read-only tools first. For add, delete, re-search, and queue operations, first identify the exact item with media_search or an overview tool. For ambiguous titles, return candidates or ask for disambiguation. Do not invent IDs. Queue actions require the queue_id from radarr_overview or sonarr_overview output. Do not claim to access torrent clients, indexers, shell, or the filesystem; this app intentionally does not expose those capabilities.

Command Interface

Consult the justfile:

just setup            # install runtime and dev dependencies with uv
just run              # run the MCP server (:17432)
just run-chat         # run the chat interface (:17433)
just test             # run MCP unit tests (no live services required)
just test-chat        # run chat unit tests (no live services required)
just test-live        # run MCP live integration tests (requires LIVE_TESTS=1)
just test-chat-live   # run chat live tests (requires LIVE_TESTS=1 + running MCP server)
just test-smoke       # call /health on the configured host/port
just format           # format with ruff
just check            # ruff lint + mypy type-check

Tool Surface

Read-only tools:

  • media_status — Radarr, Sonarr, and Plex reachability, version, health, and disk space
  • radarr_overview — movie list, download queue, and wanted/missing
  • sonarr_overview — series list, download queue, and wanted/missing episodes
  • plex_overview — active sessions, recently added, and unwatched staleness candidates
  • media_search — fuzzy search across Radarr, Sonarr, and Plex with external TMDb/TVDb lookup
  • staleness_report — cross-references Plex watch history against Radarr/Sonarr to surface old unwatched, unmanaged, and queue-stuck items

Write tools:

  • radarr_add_movie — add a movie by TMDb ID (dry-run by default)
  • radarr_research_movie — trigger a search, refresh, or downloaded-scan command on a Radarr movie
  • radarr_delete_movie — remove a movie from Radarr by internal ID (preserves files by default)
  • radarr_queue_actionremove or blocklist a stuck queue item by queue ID
  • sonarr_add_series — add a series by TVDb ID (dry-run by default)
  • sonarr_research_series — trigger a series search, refresh, or missing-episode search
  • sonarr_delete_series — remove a series from Sonarr by internal ID (preserves files by default)
  • sonarr_queue_actionremove or blocklist a stuck queue item by queue ID

Write tools require explicit identifiers. Add operations require TMDb or TVDb IDs. Delete, re-search, and queue operations require exact Radarr or Sonarr internal IDs. Queue actions never touch the torrent client (removeFromClient=false).

Example Prompts

What is currently playing on Plex?
Are Radarr or Sonarr stuck on anything?
Show movies added to Plex more than 90 days ago that no one has watched.
Is Heat already on the server? If not, prepare to add the 1995 Michael Mann film, but do not actually add it yet.
Remove the 2013 film The Heat from Radarr, but do not delete files.
Are there any stuck or import-blocked items in the Radarr or Sonarr queue? If so, clear them.

Troubleshooting

  • Missing config: run cp .env.example .env, then fill in all required values.
  • 401 from /mcp: verify Authorization: Bearer <MCP_BEARER_TOKEN>.
  • OAuth consent form rejects your token: paste the exact value of MCP_BEARER_TOKEN from your .env with no extra whitespace.
  • Claude.ai "Authorization failed" after consent: check that MCP_PUBLIC_BASE_URL is set and matches your public hostname; verify with curl https://<host>/.well-known/oauth-authorization-server that token_endpoint is a full HTTPS URL.
  • Radarr or Sonarr auth errors: verify API keys in each app.
  • Plex errors: PLEX_URL must be reachable from where the server runs. On a seedbox slot that usually means the public HTTPS URL, not 127.0.0.1:32400. Verify PLEX_TOKEN and section names (PLEX_MOVIE_SECTION, PLEX_TV_SECTION).
  • Partial status warnings: one upstream may be down while the MCP server itself is healthy.

Automated tests (just test) use mocked upstreams and do not require live services. Use just test-live to test access to external services.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors