plex-mcp
Enables browsing and searching your Plex Media Server libraries, including recently added items, on-deck content, and detailed item metadata.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@plex-mcpsearch for 'Star Wars'"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
plex-mcp
·
claude-opus-4-8[1m] · 2026-07-07 · details
An MCP server for Plex Media Server, packaged as a Docker container. Lets an MCP client (Claude Desktop, etc.) browse and search your Plex libraries.
Tools
Tool | Description |
| List all libraries (sections) on the server |
| Search across all libraries |
| Search via Plex's hub-search endpoint, including collections (unlike |
| Recently added items, optionally per-section |
| Items "on deck" (partially watched / next up) |
| Metadata for one item by rating key. Pass |
| List items in a library section (paged, optional type filter, optional |
| List collections in a library section (thin wrapper over |
| Children of an item (show→seasons, season→episodes, artist→albums) |
| Currently-playing sessions on the server |
| Playback history entries (paged, most recent first) |
| Mark an item as watched (reversible) |
| Mark an item as unwatched (reversible) |
| List all playlists (regular + smart) |
| List a playlist's contents |
| Create a regular playlist seeded with one item |
| Append an item to a regular playlist |
| Remove an item by |
| Delete a playlist (metadata only — media untouched) |
| Plex's curated server-wide hubs (Continue Watching, Recently Released, etc.) |
| Curated hubs scoped to one library section |
| Plex's curated "related" hubs for an item (provenance-grouped) |
| Algorithmic similar items for an item (flat list) |
| Re-pull metadata for an item from its current agent (optional |
| List candidate matches for an item (TMDB / TVDB / etc.); optional title/year/agent/language overrides |
| Apply a chosen match ( |
| Override scalar metadata fields (title, summary, year, etc.) with field-level locking |
| Detach an item from its agent binding (back to unmatched state); locked fields survive |
| Trigger a metadata refresh for an entire library section (incremental or deep) |
| Split a Plex item back into its constituent media variants as N separate items |
| Merge other items INTO a target item (sources absorbed; target survives) |
| Fetch poster/art/banner/clearLogo bytes for an item as an MCP image content block (so vision-capable clients can actually see the picture); optional max_width/max_height routes through Plex's transcoder |
| Same input surface as |
| Fetch the Plex Media Server's own diagnostic log bundle (a ZIP) and write it to disk under |
| List every poster candidate for an item (agent-supplied, locally-scanned, previously uploaded), including which one is currently active |
| Select an existing poster candidate as the active one, by the candidate's own |
| Add a new poster from an external URL (Plex fetches it) or a local file under |
Related MCP server: Plex Assistant MCP
Configuration
Two environment variables, both required:
Var | Example | Notes |
|
| Base URL of your Plex server |
| (see below) | Plex auth token |
To find your Plex token, see Plex's Finding an authentication token guide.
Optional environment variables
All have working defaults; set only to override.
Var | Default | Notes |
|
| Timeout for every outbound Plex request except log downloads |
|
| Size cap for |
|
| Size cap for |
|
| Timeout for |
|
| Evicts an HTTP-mode MCP session after this much inactivity |
LOG_LEVEL, MCP_ALLOWED_HOSTS/MCP_ALLOWED_ORIGINS, and
HOST_IMAGE_DIR/HOST_LOG_DIR are covered in their own sections below
(Logging, HTTP transport hardening, Portainer deploy) since each needs
more than a one-line note.
Plex on the same host as the container? Use
PLEX_URL=http://host.docker.internal:32400. The compose file mapshost.docker.internalto the Docker host gateway viaextra_hosts, so the container can reach a Plex server running on the host. The host's own hostname (e.g.my-nas) won't resolve from inside the container without that mapping.
Transport modes
Mode | When to use | How to start |
stdio (default) | Direct invocation by Claude Desktop / MCP clients |
|
Streamable HTTP | Long-lived deployment (Portainer, Compose, k8s) | Set |
In HTTP mode the server exposes:
POST/GET/DELETE /mcp— MCP Streamable HTTP endpoint (per spec)GET /health— liveness probe (used by docker healthcheck)
HTTP mode has no caller authentication — TLS (below) encrypts traffic but doesn't identify the caller. Bind only to a private network. Rely on host firewall or LAN isolation. Don't expose to the public internet without adding bearer-token auth first.
Enabling HTTPS
HTTPS is opt-in. Resolution order at startup:
Bring-your-own cert — set both
MCP_TLS_CERT_FILEandMCP_TLS_KEY_FILEto PEM file paths. Use this when terminating Let's Encrypt or an internal CA. The server reads them at startup; restart the container to pick up renewed files.Self-managed cert (recommended for LAN-only setups) — set
MCP_TLS=auto. The server generates an ECDSA P-256 self-signed cert on first start, writes it toMCP_TLS_DIR(default/data/certs), and reuses it on subsequent starts. When the cert is within 30 days of expiry it's regenerated automatically.Otherwise the server stays on plain HTTP (today's default).
Var | Default | Notes |
| unset |
|
|
| Where |
|
| Subject Alternative Names. Comma-separated |
| first DNS SAN, else | Certificate common name. |
|
| Validity period. Cert rotates when <30 days remain. |
| unset | BYO cert (PEM). Overrides |
| unset | BYO key (PEM). |
On startup the server logs the cert's SHA-256 fingerprint and
notAfter. Pin the fingerprint client-side, or trust the cert in
your OS keystore for browsers and CLI tools.
When TLS is on, the compose healthcheck needs the
--no-check-certificate flag — update the test: line to
["CMD", "wget", "--no-check-certificate", "-q", "-O-", "https://localhost:3000/health"].
Pointing mcp-remote at an HTTPS endpoint
For a self-signed cert, either pin the cert file via Node's CA bundle or skip verification on the client (LAN-only):
# Trust the server's self-signed cert (preferred):
NODE_EXTRA_CA_CERTS=./server.crt \
npx -y mcp-remote https://nas.local:3443/mcp
# Or skip verification for quick testing (LAN-only):
NODE_TLS_REJECT_UNAUTHORIZED=0 \
npx -y mcp-remote https://nas.local:3443/mcpReverse-proxy alternative
In-process TLS is convenient when you don't already run an ingress
controller. If you have Caddy, Traefik, or nginx in front of your
home services, the more idiomatic pattern is to terminate TLS at
the proxy (with automatic Let's Encrypt) and keep plex-mcp on
plain HTTP behind it. The two approaches are interchangeable — pick
whichever matches your existing setup.
Run with Docker (stdio, on demand)
docker build -t plex-mcp .
docker run -i --rm \
-e PLEX_URL=http://192.168.1.50:32400 \
-e PLEX_TOKEN=your-token \
plex-mcpRun with Docker Compose (HTTP, long-lived)
The compose file pulls ghcr.io/carldog/plex-mcp:latest (multi-arch:
linux/amd64 + linux/arm64), published by CI on every push to main.
# Required env vars (or use a .env file):
export PLEX_URL=http://192.168.1.50:32400
export PLEX_TOKEN=your-token
export MCP_ALLOWED_HOSTS=nas.local:3001 # required — see below
export HOST_PORT=3001 # optional, defaults to 3001
docker compose upThe MCP endpoint will be at http://<host>:${HOST_PORT}/mcp.
To rebuild from source instead of pulling:
docker build -t ghcr.io/carldog/plex-mcp:latest .
docker compose upDeploy via Portainer (Stack from Git)
In Portainer, Stacks → Add Stack → Repository.
Repository URL:
https://github.com/CarlDog/plex-mcpCompose path:
docker-compose.ymlEnvironment variables: set
PLEX_URL,PLEX_TOKEN,MCP_ALLOWED_HOSTS,HOST_IMAGE_DIR, andHOST_LOG_DIR— all required (see below); optionallyHOST_PORT.Deploy. Healthcheck reaches green within ~10 seconds.
MCP_ALLOWED_HOSTS is required in HTTP mode
Comma-separated list of Host header values the server accepts on
/mcp — e.g. nas.local:3001 (must match whatever host:port a client
actually dials, including the mapped HOST_PORT). The server refuses
to start in HTTP mode without it, and docker compose config fails
the same way if it's unset — both fail before the container ever
comes up, deliberately, rather than starting in a silently-unprotected
state.
This exists because binding 0.0.0.0 inside a container isn't a real
access boundary the way loopback binding is on a bare host: a page
loaded in a browser anywhere on the LAN can perform DNS rebinding —
pointing its own hostname at this container's IP — and drive tools
(including writes like plex_delete_playlist) as a confused deputy,
entirely bypassing "LAN-only, no bearer token" as a security posture.
The Host allowlist closes that gap without requiring full
authentication. MCP_ALLOWED_ORIGINS (optional, default empty) does
the same for the Origin header — leave it unset unless a
browser-based client legitimately needs to call this server directly;
non-browser clients (the mcp-remote bridge, a direct fetch) never
send an Origin header, so the empty default only ever rejects the
request shape a DNS-rebinding attack actually sends.
HOST_IMAGE_DIR and HOST_LOG_DIR are required — no relative default
Both volume host paths are ${VAR:?...} in the compose file: there
is no fallback default, so docker compose up / a Portainer
redeploy fails fast with a clear error if either is unset, rather than
starting in a broken state.
This used to be a soft ${VAR:-./data/images} default, which is only
safe for a local docker compose up from a stable clone. In a
Portainer git stack it's a trap: every redeploy clones the repo into a
fresh per-commit directory (/data/compose/<stack-id>/<commit>/),
where a relative path like ./data/images doesn't exist. Docker
refused the bind mount and the container was left stuck in created
state — it never started. That also hit automatic redeploys (image
update, git poll), so a previously healthy stack went down with no
manual action; the only symptom was the container sitting in
created. This took the deployed stack down for ~10 hours on
2026-07-31 — see docker-deployments.md rule #10 and fleet lesson
2026-07-31-relative-compose-volume-defaults-break-portainer-git-stacks.
The compose file now makes the requirement structural instead of a
documentation-only convention.
Set both to absolute host paths in the stack's environment variables:
HOST_IMAGE_DIR— theplex_save_imageoutput directory. Recommended: the host directory backing filesystem-mcp's/media/_mcp-scratchmount — e.g./volume1/Media/_mcp-scratchon a Synology NAS — which keeps theplex_search → plex_save_image → filesystem-mcppipeline on one shared directory.HOST_LOG_DIR— theplex_download_logsoutput directory, kept separate fromHOST_IMAGE_DIRsince a diagnostic ZIP isn't a media artifact — e.g./volume1/docker/plex-mcp/logson a Synology NAS (matching this fleet's per-container appdata convention).
Make sure both directories exist on the host before the first deploy: Docker does not auto-create a missing bind-mount source, it just refuses to start the container.
Use with Claude Desktop
stdio (local invocation)
{
"mcpServers": {
"plex": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "PLEX_URL", "-e", "PLEX_TOKEN",
"plex-mcp"
],
"env": {
"PLEX_URL": "http://192.168.1.50:32400",
"PLEX_TOKEN": "your-token"
}
}
}
}HTTP (remote MCP server)
{
"mcpServers": {
"plex": {
"url": "http://nas.local:3001/mcp"
}
}
}(Requires Claude Desktop or a client that supports remote MCP HTTP.)
Local development
npm install
cp .env.example .env # then edit
PLEX_URL=... PLEX_TOKEN=... npm run dev # stdio
MCP_PORT=3000 MCP_ALLOWED_HOSTS=localhost:3000 PLEX_URL=... PLEX_TOKEN=... npm run dev # HTTPLogging
The server emits structured logs to stderr (stdout is the MCP wire protocol in stdio mode and must not be polluted). Format:
2026-04-29T15:30:00.000Z INFO [tool:plex_browse] invoke section_id=7 type=show limit=2
2026-04-29T15:30:00.337Z INFO [tool:plex_browse] ok ms=337Configure verbosity via the LOG_LEVEL env var (default info):
Level | Shows |
| Errors only |
| + 4xx Plex responses |
| + Tool invocations and completions |
| + Every Plex API call with method, path, status, ms |
| (reserved) |
Container logs are captured by Docker's json-file driver and
rotated automatically (10MB × 3 files = ~30MB cap; oldest deleted
on rotation). View with docker logs plex-mcp or docker logs -f.
Security
The container runs as a non-root user (
plexmcp).The Plex token is passed via env var — never bake it into the image.
A
.githooks/pre-commitruns gitleaks on every commit. Activate it once per clone:git config core.hooksPath .githooks
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseAqualityDmaintenanceA Python-based MCP server that integrates with Plex Media Server API to search for movies and manage playlists in your Plex media library.96
- AlicenseNot gradedqualityDmaintenanceEnables users to manage and control their Plex media library through natural language commands in MCP-compatible AI clients. It supports searching content, managing playlists, tracking library statistics, and monitoring live viewing sessions.MIT
- AlicenseNot gradedqualityBmaintenanceMCP server for reelgrep - browse and search your local video library from any MCP client.12MIT
- AlicenseAqualityCmaintenanceMCP server for Plex Media Server, focused on media discovery, search, library management, and playback control.25MIT
Related MCP Connectors
Search and browse every MCP server in the Model Context Protocol registry.
Remote MCP server for full read/write access to a Zotero library
OMDb MCP — IMDB-derived movie / TV / episode data (BYO key)
Appeared in Searches
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/CarlDog/plex-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server