Skip to main content
Glama
dustinhansen

Plex MCP Server

by dustinhansen

plex-mcp

An MCP server that gives your AI assistant a complete picture of your Plex Media Server: every library, watched and unwatched, every household member's watch history, plus safe curation writes (playlists, collections, watched state, ratings, the plex.tv watchlist).

It talks to Plex's HTTP API directly (no client library, 2 runtime dependencies), ships as an npm package and a Docker image, and is tested offline.

Nothing in this server can delete, move, or modify media files or metadata.

What you can ask

  • "Which Scorsese films haven't I seen?"

  • "Pick three unwatched 90s thrillers under 100 minutes."

  • "What should I continue tonight?" (the next unwatched episode of each show)

  • "Has anyone else watched Severance? Who's furthest along?"

  • "What did the kids watch this month?"

  • "What's new in the TV library this week?"

  • "Make a collection of everything Denis Villeneuve directed that I haven't rated."

Related MCP server: Plex MCP Server

Quick start

Requires Node 22+ and a Plex Media Server you own.

1. Sign in and get your credentials (plex.tv PIN flow, no token hunting):

npx @dustinhansen/plex-mcp login

It prints a link, waits for you to approve it in the browser, then prints PLEX_URL=… and PLEX_TOKEN=…. Add --write <file> to append both lines to a file as well. The file holds your token, so on macOS and Linux a new one is created readable by you only. An existing file keeps its permissions. (Prefer the manual route? Plex Web → play any item → ⋮ → Get Info → View XML → X-Plex-Token in the URL.)

2. Add it to your client. Any MCP client that can launch a local (stdio) server works. Most of them take the server as a command, its arguments and an environment. Here are 2 CLIs, for example:

# Claude Code
claude mcp add plex -e PLEX_URL=http://<plex-host>:32400 -e PLEX_TOKEN=<token> -- npx -y @dustinhansen/plex-mcp

# Codex
codex mcp add plex --env PLEX_URL=http://<plex-host>:32400 --env PLEX_TOKEN=<token> -- npx -y @dustinhansen/plex-mcp

Clients configured with a JSON file (Claude Desktop, Cursor, and any client that reads a .mcp.json) mostly use this shape. Your client's docs say where the file lives and whether it names the top-level key differently:

{
  "mcpServers": {
    "plex": {
      "command": "npx",
      "args": ["-y", "@dustinhansen/plex-mcp"],
      "env": {
        "PLEX_URL": "http://<plex-host>:32400",
        "PLEX_TOKEN": "<token>"
      }
    }
  }
}

Assistants that run in a browser or on a phone (claude.ai, ChatGPT) can't launch a local process. They need the server in HTTP mode, reachable from the vendor's cloud. See HTTP mode and Docker and docs/remote-access.md.

Use the server owner's admin token if you want every user's watch history.

Tools

The server registers 22 tools by default, and the optional ones below add up to 4 more. Each tool declares a title and all 4 MCP annotations (read-only, destructive, idempotent, open-world). The spec tells clients to treat annotations as hints, so the Safety model rests on what the server registers.

Browse & read

tool

what it does

list_libraries

All sections with libraryType (Movies / TV Shows / Music / Photos / Other Videos), scanner type, agent, and whether a scan is running.

list_media

Any library, any kind (movie/show/season/episode/artist/album/track/collection). Filters: watched, rated, genre, year, decade, contentRating, collection, country, studio, maxDurationMin, addedSince, lastViewedBefore, lastViewedAfter. The last-viewed filters take an ISO date or a relative value like -3y. A never-watched item counts as last viewed before any date, so pair lastViewedBefore with watched: "watched" to leave those out. Sorts: title, year, rating, audienceRating, userRating, addedAt, lastViewedAt, viewCount, duration, random. Paginated (offset/limit/nextOffset). fields projects each record to the keys you need. detail: true adds fields such as cast, crew and a summary to each record, and caps limit at 25.

search

Global search across libraries incl. people, collections, playlists. externalId (imdb:tt0113277, tmdb:949, tvdb:12345) looks a movie or show up by the id Plex holds for it instead.

get_item

Full metadata + file info (paths, codecs, sizes, every version) by ratingKey.

get_children

Children of a show (seasons), season (episodes), artist (albums), album (tracks), collection (items) or playlist (items, with playlist: true). Paginated (offset/limit/nextOffset). fields projects each record to the keys you need. detail: true adds fields such as cast, crew and a summary to each record, and caps limit at 25.

get_extras

Trailers, featurettes, behind-the-scenes for an item.

person_credits

Filmography by director/actor/writer/composer, with watched filter. Paginated (offset/limit/nextOffset). fields projects each record to the keys you need. detail: true adds fields such as cast, crew and a summary to each record, and caps limit at 25.

library_changes

What's new or updated since a cutoff (-7d or ISO date), as an added and an updated list, each capped by limit. detail: true adds fields such as cast, crew and a summary to each record, and caps limit at 25 for each list.

library_stats

Totals watched and unwatched, with genre/decade/rating breakdowns and optional per-user play counts.

Discover

tool

what it does

similar_items

Plex's "more like this" for an item, with a watched filter.

random_pick

N random items matching any list_media filter set ("surprise me, unwatched, 80s, under 2h"). Each pick is the same record list_media returns. detail: true adds fields such as cast, crew and a summary to each record. user picks as a Plex Home user, so unwatched means unwatched by them.

continue_watching

Plex's Continue Watching row: the next unwatched episode per in-progress show and any partially watched movie, with how far into each you are. detail: true returns full item records instead, and caps limit at 25.

get_watchlist

The server owner's plex.tv watchlist, with whether each title is in a library here and its ratingKey if so. Paginated (offset/limit).

There are also two MCP prompts, movie_night and whats_new, and a plex://libraries resource.

History & household

tool

what it does

watch_history

Who watched what, and when. Filter by user, library, time window, or item.

now_playing / list_users

Live sessions and every account on the server.

continue_watching, list_media, random_pick and library_stats accept a user argument to see the library as a Plex Home user (their watched state, their on-deck). Plex only allows this for Home (managed) users. For friends, watch_history is the view that works for everyone.

Curate (writes)

Registered unless PLEX_READ_ONLY=1.

tool

what it does

set_watched

Mark items watched/unwatched (explicit ratingKeys only).

rate_item

Set or clear the server owner's star rating.

manage_playlist / manage_collection

Create, add, remove, delete. Deleting removes only the grouping, never media.

manage_watchlist

Add movies and shows in the library to the server owner's plex.tv watchlist, or remove them (explicit ratingKeys only).

scan_library

Trigger "Scan Library Files" for a section, optionally one folder.

manage_watchlist resolves each ratingKey to the Plex GUID plex.tv identifies the item by. It takes movies and shows only, the kinds a Plex watchlist offers, and refuses an item matched by a legacy agent (which has no Plex GUID). Both checks run before anything is sent.

Optional: playback (off by default)

Set PLEX_ALLOW_PLAYBACK=1 to register list_clients and play_item ("play this album on my phone"). Both tools use Plex's player-control API, so the target must be a player that advertises itself to the server. We have tested playback with Plexamp. Current Plex apps for TVs and desktops may not show up in list_clients at all: Plex for Apple TV and Plex for Mac did not, even with the app open. Off by default because it's the one thing here that touches the real world. PLEX_READ_ONLY=1 keeps them unregistered even when this is set.

Optional: Tautulli

Set TAUTULLI_URL and TAUTULLI_API_KEY to add tautulli_stats (home stats: most watched, most active users, popular items over a period) and tautulli_history (per-play records with platform, player, and percent watched). Plex's own history covers the basics. Tautulli adds the detail most admins already collect. They need Tautulli 2.18.0 or later, the first release that reads the API key from a request header.

Safety model

  • No media mutations. There is no code path that deletes, moves, renames, or edits media files or metadata. Playlist/collection deletion removes the grouping only.

  • Writes are explicit. Every write names its targets: items by ratingKey, a playlist or collection by id, and a library by title. A library scan can narrow to one folder. Creating a collection names its library too: several read tools default to Movies because a read changes nothing, but a write never picks its own target. The collection's kind is not a target: it follows the named library (movie, show, or album for a music library), so it always agrees with where the collection goes. A kind that contradicts the library is refused before the write, and so is a library type that holds no collection kind the tool offers. Nothing operates on "all matches".

  • Playback is opt-in via an environment variable. The tools aren't registered without it.

  • PLEX_READ_ONLY=1 removes every write. The curation writes and the playback tools are not registered, whatever PLEX_ALLOW_PLAYBACK says, so a client sees only reads. The Tautulli tools are reads and stay. The restriction wins over the opt-in because a server set up to change nothing should not start changing things when another variable is added. Any value other than 1, 0 or empty stops the server rather than starting it with writes.

  • Ids are a run of digits. A tool refuses any ratingKey, playlist id or collection id with anything else in it before the request reaches Plex (the client also refuses a request path that does not resolve to itself). We made this decision because a tool puts the id into the request path, and Plex marks an item watched, rates it and refreshes a library over GET. An id carrying .. or ? could turn a read into one of those writes, even with PLEX_READ_ONLY=1. We refuse a bad id rather than encode it, because no real Plex id needs encoding.

  • Errors never echo upstream response bodies from Plex, plex.tv or Tautulli, which can contain server paths and configuration. A failed request names the upstream, the path and either the HTTP status or the network cause, such as Plex unreachable (ECONNREFUSED) on /library/sections when PLEX_URL points at the wrong host or port.

  • HTTP mode refuses to start without a secret. See below.

HTTP mode and Docker

By default the server speaks MCP over stdio. Set MCP_TRANSPORT=http to serve streamable HTTP on PORT (default 8627) with a /health endpoint. This is what the Docker image does. It implements MCP protocol revision 2025-11-25, the one the SDK's 1.x line speaks, not the newer 2026-07-28 revision. Outside the image, HTTP mode listens on 127.0.0.1 only. Set MCP_HOST=0.0.0.0 to accept connections from other machines. The image is published to ghcr.io/dustinhansen/plex-mcp for linux/amd64 and linux/arm64, so the usual setup is Docker Compose on whatever already runs your containers:

services:
  plex-mcp:
    image: ghcr.io/dustinhansen/plex-mcp:latest
    hostname: plex-mcp
    restart: unless-stopped
    ports:
      - "127.0.0.1:8627:8627"
    environment:
      PLEX_URL: ${PLEX_URL:?}
      PLEX_TOKEN: ${PLEX_TOKEN:?}
      MCP_PATH_SECRET: ${MCP_PATH_SECRET:?}

The port is published on 127.0.0.1 only, so nothing beyond this host can reach it. To reach it from other machines on your LAN, see docs/remote-access.md.

The ${VAR:?} form makes a missing value fail the deploy loudly instead of starting an unauthenticated container. hostname keeps the identifier the container sends plex.tv the same when it is recreated (see Client identifier). The image carries its own HEALTHCHECK against /health, so the service needs no healthcheck: block. Put the 3 values in a .env next to the compose file (never in the image or a repo):

PLEX_URL=http://<plex-host>:32400
PLEX_TOKEN=<token>                        # from `npx @dustinhansen/plex-mcp login`
MCP_PATH_SECRET=$(openssl rand -hex 32)   # the endpoint's only auth, so treat it as a password

To try it without compose:

docker run --rm --hostname plex-mcp -p 127.0.0.1:8627:8627 \
  -e PLEX_URL=http://<plex-host>:32400 -e PLEX_TOKEN=<token> \
  -e MCP_PATH_SECRET=$(openssl rand -hex 32) \
  ghcr.io/dustinhansen/plex-mcp:latest

The MCP endpoint is http://127.0.0.1:8627/<MCP_PATH_SECRET>/mcp. The secret is the only auth on a write-capable endpoint, so the server refuses to start in HTTP mode without one (MCP_ALLOW_NO_AUTH=1 overrides, for listeners that are unreachable from untrusted networks).

  • docs/remote-access.md: connecting a desktop or cloud-hosted MCP client to an HTTP-mode server over your LAN or a public HTTPS endpoint, and what the security trade-offs are.

  • docs/docker.md: more on the compose setup, keeping the image up to date (Watchtower/Diun, version pinning) and building locally.

Environment variables

variable

required

purpose

PLEX_URL

yes

e.g. http://<plex-host>:32400 (a value that is not an http or https URL is refused at startup)

PLEX_TOKEN

yes

owner/admin X-Plex-Token

MCP_TRANSPORT

no

stdio (default) or http (any other value is refused at startup)

PORT

no

HTTP listen port, an integer from 0 to 65535 (default 8627)

MCP_HOST

no

HTTP listen address (default 127.0.0.1, or 0.0.0.0 in the image)

MCP_PATH_SECRET

http mode

capability-URL secret, required in http mode (at least 16 characters of letters, digits, -, _, . or ~)

MCP_ALLOW_NO_AUTH

no

1 to run http mode without a secret (trusted networks only)

PLEX_ALLOW_PLAYBACK

no

1 registers list_clients / play_item

PLEX_READ_ONLY

no

1 registers no write or playback tools, whatever PLEX_ALLOW_PLAYBACK says (0, empty or unset registers them)

TAUTULLI_URL, TAUTULLI_API_KEY

no

registers the Tautulli tools when both are set (one alone is refused at startup, as is a TAUTULLI_URL that is not an http or https URL)

Verifying what you install

Both artifacts are built and published by release.yml from a tagged commit of this repository, and each carries provenance that says so.

  • npm. The package page on npmjs.com shows a provenance badge for each version. In a project that has the package installed, npm audit signatures checks the registry signatures and the provenance attestation.

  • Image. gh attestation verify oci://ghcr.io/dustinhansen/plex-mcp:vX.Y.Z --repo dustinhansen/plex-mcp checks the image's build provenance against this repository.

CI and releases

.github/workflows/ci.yml runs on every pull request into main, every push to main and on demand, on GitHub-hosted runners only:

  • build: the type checks, the compile-only type fixtures and the offline test suite (npm test), on Node 22.

  • artifact-smoke: the packed npm tarball installed with --omit=dev --offline and started over stdio, on Node 22 (the engines floor) and Node 24 (an LTS line newer than the floor).

  • container-smoke: the image built for linux/amd64 and linux/arm64, each on a runner of that architecture with no emulation, then booted against a local Plex fixture.

  • server-json: server.json checked by the MCP registry's validate endpoint with mcp-publisher validate, the same pinned release release.yml publishes with. It sends the file to the registry and publishes nothing.

Its token is read-only and no job reads a secret, so a pull request from a fork runs every check. test/build/workflow-contract.test.ts, part of npm test, holds both workflows to their shape: actions pinned to full commit SHAs, a timeout on every job, a read-only default token, and write scopes and release secrets only where an allowlist names them.

.github/workflows/release.yml runs only when a GitHub release is published by hand. It re-runs the CI checks against the tag, checks the tag against every version field, then publishes:

release

npm @dustinhansen/plex-mcp

image ghcr.io/dustinhansen/plex-mcp

MCP registry

vX.Y.Z

X.Y.Z on dist-tag latest, with provenance

:vX.Y.Z for amd64 and arm64, attested, and :latest moves to it

io.github.dustinhansen/plex-mcp at X.Y.Z

vX.Y.Z-rc.N, marked prerelease

X.Y.Z-rc.N on dist-tag next, with provenance

:vX.Y.Z-rc.N, attested, and :latest stays put

not published

The release checklist and the repository's settings are in docs/publishing.md.

Development

npm install
npm test          # build emitted JavaScript, then run offline compatibility tests
npm run smoke     # build, then live checks against a real server (needs PLEX_URL + PLEX_TOKEN)

The smoke suite checks every list_media filter against a live server: watched, rated, year, decade, genre, content rating, collection, country, studio, duration, added-since, last-viewed-before and last-viewed-after. It checks a Home user's view too, when there is one. Each filter check needs a match, except unwatched and unrated. Unwatched plus watched must equal the whole library, and so must unrated plus rated. When the server has nothing to test a filter with, the check logs a skip line naming the filter.

CHANGELOG.md tracks releases.

Design notes

Limited surface area

plex-mcp registers a small set of tools rather than covering the whole Plex API. We made this decision because every registered tool costs your MCP client context before a single call is made, and a model does better with a set it can hold in context. We try new capability first as a parameter on an existing tool. You'll see that list_media carries the filters a dozen per-kind tools would otherwise have spread out. The same rule gives each Plex list 1 tool. continue_watching reads the on-deck list, and we named it for the Continue Watching row you see in Plex's UI. Its detail parameter returns the full item records, so a second tool for the same list would add nothing. So if Plex can do something the tools don't expose, that's something we left out on purpose.

But we're open to adding more surface in the future. Please open a feature request issue and tell us what you'd use it for, because the use is what tells us which existing tool could take it as a parameter. We take requests in the issue tracker rather than GitHub Discussions, since that's the one place I triage.

Tautulli

The Tautulli tools are here because they answer what Plex's own history can't. Plex records who played what and when, but it only knows the player and how far a play got while the session is live. Tautulli keeps the platform, player and percent watched for every play, and ranks the most watched items and most active users over however many days you ask for. No parameter on an existing tool could return that.

They still fit the limited surface. Neither tool is registered unless you set both TAUTULLI_URL and TAUTULLI_API_KEY, so they cost your client no context otherwise. Both are reads, so they stay under PLEX_READ_ONLY=1, and the Safety model's rule against echoing upstream response bodies covers them.

The API key travels in the X-Api-Key header and never in the request URL. We made this decision because a URL lands in the logs of any proxy between plex-mcp and Tautulli, and in Tautulli's own access log, while a header usually does not. Tautulli's API reference prefers the header for the same reason. Tautulli reads the header from 2.18.0 on, so that version is the minimum. If you run an older Tautulli, every call fails with a missing-key error (HTTP 400), and the error tells you to check the version first. We rejected keeping the key in the URL and saying so, because that documents the exposure instead of removing it. We also rejected trying the header first and falling back to the URL, because the fallback could not tell an older Tautulli from a wrong key or a bad request, and each time it ran it would put the key back in the URL.

Watchlist

The watchlist takes library items only. We made this decision because any write that picks its target from search results breaks the rule that every write names its targets. You'll see that manage_watchlist names items by ratingKey like every other write, and a title in no library here has no ratingKey (adding one would need a plex.tv Discover search by title). get_watchlist still shows those titles, with ratingKey null.

The list itself lives on plex.tv, not on your server. It sits with the curation writes anyway, because it's the server owner's "I want to watch this" list.

Remote access

Remote access works by capability URL, and the server checks the secret itself. We put the check in-process because any reverse proxy that forwards all paths publishes every path, bare /mcp included. The path secret in the endpoint's URL authorizes every request. The default transport, stdio, has no network surface at all. HTTP mode is streamable HTTP (not the deprecated SSE transport) and stateless (every request builds a fresh server, and all real state lives in Plex). See docs/remote-access.md.

HTTP mode follows the transport's 2 local-safety rules. It listens on loopback unless MCP_HOST says otherwise, and it answers 403 to a browser Origin that isn't loopback, the specific address MCP_HOST names, or null. A wildcard MCP_HOST such as the image's 0.0.0.0 names no address. The check stops DNS rebinding (a rebound page's Origin names the attacker's domain), and it runs before the path secret is checked. We rejected matching the request's Host header instead, because a rebound request's Host names the same domain and would get through. A null origin, sent by local files and sandboxed pages, passes. The server sends no CORS headers, so such a page can't read the response, and it still needs the secret. There is no allowed-origins setting. The clients in docs/remote-access.md are not browser pages and send no Origin, so if you host a browser client on some other domain, it's refused.

The listen address is MCP_HOST, not the generic HOST. We named it that because a shell or CI environment may export HOST for its own reasons, and an inherited HOST=0.0.0.0 would silently move the bind address off loopback. So the server ignores HOST. We kept PORT under its plain name because an inherited port can make the server unreachable, but it can't move it off loopback.

Optional spec features

We left a few optional MCP spec features out on purpose. No tool declares an outputSchema, because claude.ai drops any tool whose output schema carries the draft-07 $schema stamp the SDK adds. I found that on another MCP server of mine. There are no sessions, because HTTP mode is stateless (above). There is no per-tool rate limiting either. Over stdio there is 1 client and nothing to limit, and over HTTP the path secret and the 1 MiB request-body cap are the mitigations. On either transport, one tool call may carry at most 1000 array elements and object members combined (twice the longest ratingKeys list any tool takes), so a request under the body cap can't exhaust memory while its arguments are checked.

But we're open to adding these in the future. If your client needs one, please open a feature request issue and say what it would let you do.

Releases

Everything we publish comes from a release, never from main. We set it up that way so the npm package and the image you install can each be traced back to the tagged commit that built it (both carry build provenance). A GitHub release, published by hand, is the only trigger for release.yml, and it publishes the npm package, the image and the registry entry together.

I cut releases by hand because release automation such as Release Please opens its release PR from a workflow, and GitHub holds the checks on that PR until someone approves them by hand, unless it is opened with a long-lived token. I release occasionally and on my own, so the automation isn't worth keeping that token around. CI runs only on GitHub-hosted runners, and the arm64 image builds natively on an arm64 runner rather than under emulation.

Registry entry

The registry entry lists the image beside the npm package. We list both because an npm-only entry would hide the always-on HTTP server from anyone who finds plex-mcp through the official MCP registry (the npm package runs over stdio, 1 process per client). The image's entry describes the safe local default: the port published on loopback only, and the path secret in the endpoint URL. If you want LAN or remote exposure, docs/remote-access.md covers it. The image carries the io.modelcontextprotocol.server.name label the registry's package docs ask for as proof of ownership, and a test keeps that label, server.json, and package.json's mcpName and version in step.

Published port

Every copy-paste example here publishes the port on loopback only. We made this decision because people copy from this page, so whatever the examples publish becomes the default without anyone choosing it. The compose and docker run examples here and in docs/docker.md publish 127.0.0.1:8627:8627, the same mapping as the registry entry, and a test keeps all 3 in step. (The image sets MCP_HOST=0.0.0.0, since a container that listens on its own loopback is unreachable through any port mapping, so the mapping alone decides who can reach the server.)

We rejected keeping 8627:8627 in the examples and rewording the registry note to say they expose the port to the LAN. The path secret still guards a LAN-wide port. But the README would then describe a different safe default from the registry entry's.

Registry validation

Every pull request asks the registry whether it accepts server.json. We do this because a release publishes the server.json in its tagged commit, and a published release is immutable, so a file the registry rejects at that point can only be fixed by shipping the next version. The schema alone doesn't catch everything the registry refuses, and without the check a release's own publish would be the registry's first look at the file. So the server-json CI job runs mcp-publisher validate at the version release.yml pins. Because release.yml runs the CI checks first, every release asks again before it publishes.

We rejected a one-off check before the first release, because every later release edits the file. We also rejected a check only inside release.yml, because it would run for the first time during the first release, and from then on it would fail only after the version was already tagged. The job's download URL and SHA-256 are release.yml's, and the workflow contract test fails if they differ, so moving to a new mcp-publisher means editing both workflows. The cost is 1 call to the registry per CI run, and while the registry is down the check fails until someone re-runs it.

Public history

The public history starts at 0.9.0, from a single squashed commit rather than a rewritten copy of the private history I developed it in. We chose that because a curated rewrite is too easy to get wrong, and a force-push doesn't scrub what a host keeps on its own, such as the refs of closed pull requests. Changes after 0.9.0 land as pull requests in this repository.

Default port

The default port is 8627. I wanted this service to have its own default port. I moved it off 3300 because IANA assigns 3300 to Ceph monitors (Proxmox's Ceph integration opens it), and it sits in the 3300–3399 range SAP gateways use, so running the container with its defaults on a Proxmox+Ceph node would collide with the storage cluster. 8627 is unassigned in IANA's registry, and it avoids Plex, the common media-server companion apps, Ceph's 6800–7568 OSD range, Kubernetes' 30000–32767 NodePort range, and the crowded 3000/5000/8000/8080 development-server ports. On a phone keypad, 627 spells MCP. If you need another, set PORT.

Runtime

The server runs as emitted JavaScript. We ship it that way so an install carries only dist/ and the production dependencies, with no TypeScript compiler. TypeScript is checked and compiled before the tests, the smoke run, packaging and the Docker build. Source keeps ESM .js import specifiers so Node resolves the same module graph after the build. The tests and scripts are TypeScript too, run by Node's own type stripping with no build step of their own, and they import the compiled dist/ so they exercise what ships. Type stripping checks nothing, so npm run typecheck runs a second tsc pass over them (test/tsconfig.json).

Container image

The container image is Debian slim (node:*-slim), not Alpine. We moved off node:*-alpine because Node's musl build reliably tripped a QEMU user-mode emulation bug when CI cross-built arm64 (qemu: uncaught target signal 4 (Illegal instruction)), and then the build hung instead of failing cleanly. Debian's glibc-based Node doesn't hit that code path, and the extra image size isn't worth optimizing back into the bug. CI builds arm64 natively now, with no QEMU involved, but that bug is why the image stays on Debian slim.

If you add a compose healthcheck:, don't have it call wget or curl. The slim image has neither, so the probe fails every time while the server answers normally. The image's own HEALTHCHECK runs node -e with fetch instead.

Plex quirks

Paging works because the code sends container paging as HTTP headers (Plex silently ignores the query parameters). Person, genre, collection and country filters take names, and the code resolves them to Plex tag IDs. Studio is matched by name instead (Plex lists a studio's ID as its name URL-encoded twice, and the filter never matches that). An unknown library or user gets an error that lists the valid ones.

A per-user view switches to that user's own token where Plex allows it (Home users), because viewCount reflects the requesting account. Plex doesn't allow it for friends. For them, watch_history is the view that works, and library_stats counts each account's plays from watch history. Switching to a Home user takes 2 steps on plex.tv (plex.tv switches the user by uuid, not numeric id, and the server refuses the plex.tv token that comes back, so the code trades it for the per-server access token plex.tv lists for that user). A PIN-protected Home user is refused before any switch, with an error that points at watch_history, because plex.tv switches to that user only with their PIN. We will not take a PIN as a tool argument, because every tool argument passes through the AI conversation and the PIN would sit in its history.

search with externalId finds a movie or show by its IMDb, TMDB or TVDB id, even though Plex's own guid= filter can't (on the current Plex agents it matches only an item's own plex:// guid). So the tool reads each movie and show library with those ids attached and matches them in-process. On a server with about 4,800 items in its video libraries, reading them all took under 2 seconds.

play_item puts the item in a play queue and then tells the player to play that queue. We made this decision because Plexamp accepts a play command that has no queue (it answers 200) and then plays nothing, so the tool would report success for a play that never started. Plex makes a play queue only for a request that names its client, so both playback requests carry the same identifier login uses (see Client identifier).

Client identifier

Each machine that runs plex-mcp login shows up on plex.tv as its own device, so you can tell your installs apart in Authorized Devices and remove one without signing out the rest. The identifier plex-mcp sends is plex-mcp- plus a hash of the machine's hostname and your user name. The same machine keeps it across restarts, and no file has to hold it.

We considered one fixed identifier for every install. We rejected it because plex.tv treats one identifier as one device. When we signed in twice under the same identifier, plex.tv returned the same token both times and listed a single device, and removing that device signed out both. We also considered a random identifier saved to a file on first run. We rejected that because it needs a writable path, the container keeps no state, and login writes nothing unless you pass --write.

A token belongs to the device that ran login. If you run the container with a token from npx @dustinhansen/plex-mcp login, the device is the machine you ran that on, and 2 installs given the same token are one device. To keep 2 installs apart, run login for each from a different machine or user account. The compose and docker run examples set the hostname to plex-mcp so the identifier the container sends stays the same when it is recreated (a container's hostname is its container ID unless you set one).

Cached names

Library and account names are cached for as long as the server runs. When a name you ask for isn't in the cache, the server reads the list from Plex again once before saying it doesn't exist. We chose this because a library added or renamed while the container runs is exactly a name the cache doesn't have yet, so it's found with no restart and with no extra request for a name already known. We rejected a timed cache because it adds a clock to catch 1 more case (a deleted library asked for by its old name), and that case already ends in a Plex 404. The Plex Home user list from plex.tv is cached the same way, so a call with a user argument doesn't wait on plex.tv each time. A Home user can change in ways a library can't (a PIN added or removed, a user who left the Home), so after a PIN refusal or a failed switch, the next call reads the list again. A Home user's switched token is dropped when Plex answers 401 to it, since a token Plex refuses (access revoked, say) would otherwise fail every call until a restart. The next call switches again.

A Home user you put a PIN on while the server runs isn't refused until the server reads the Home-user list again (after a restart, a name it doesn't know, or a refused or failed switch) or Plex answers 401 to their token. Until then, calls with their user argument keep working. We left this gap open because the PIN refusal only reflects what plex.tv lets the server do. plex.tv switches to a protected user only with their PIN, and the token the server already holds was issued before the PIN existed. Everything that token can read, the server owner's token can read too, so the gap shows nothing new to whoever runs the server. We rejected re-reading the user list on a timer, since that adds the clock we rejected for libraries above. We also rejected a re-read on every call with a user argument, because it brings back the plex.tv wait the cache exists to remove.

Dependencies

plex-mcp has 2 runtime dependencies, @modelcontextprotocol/sdk and zod, and no test framework. We kept it this small because a server you run next to your media library, holding the server owner's token, should be quick to audit. Tests run on node:test with an injected fetch standing in for Plex.

Out of scope: media mutation of any kind (see Safety model), Plex server administration, and *arr-stack integration.

License

MIT. See LICENSE.

Available Tools

22 tools
continue_watchingContinue watchingA
Read-onlyIdempotent

What to continue tonight: the next unwatched episode for every show in progress (and partially-watched movies), with how far in you are. Optional library narrows to one section. user shows a Plex Home user's list. detail=true caps limit at 25 and returns full item records (watched, viewCount, actors, collections…) instead of the progress fields.

NameRequiredDescriptionDefault
userNosee the library as this Plex Home user (name or id)
limitNo
detailNo
libraryNo

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), and the description adds real behavior beyond them: detail=true silently caps limit at 25 and swaps the payload from progress fields to full item records. That cap-and-switch interaction is exactly the kind of hidden trait that prevents bad calls. It stops short of describing ordering or default page size.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences, front-loaded with what the agent gets, then the filters, then the mode switch. The parenthetical field list ('watched, viewCount, actors, collections…') is informative rather than filler, and nothing repeats the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description usefully sketches both return shapes and the truncation rule, which is what an agent needs to pick a mode. It doesn't state result ordering or that the default limit is 20, so it isn't fully airtight, but nothing critical is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is only 25%, so the description carries the burden — and it does: library is explained as a section filter, user as a Plex Home user selector, and detail's dual effect (cap + payload shape) is spelled out. Only `limit` is left to the schema, which supplies min/max/default, so the gap is minor.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a very specific resource — the next unwatched episode per in-progress show plus partially-watched movies — with the delivered value (progress position) stated up front. The 'in progress' scope naturally separates it from siblings like watch_history and now_playing without needing to name them.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied through the optional narrowing flags ('optional library narrows to one section', 'user shows a Plex Home user's list'), but it never states when to prefer this over watch_history, now_playing, or search, nor any exclusions. An agent can infer the use case from the purpose but gets no routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_childrenGet childrenA
Read-onlyIdempotent

Children of a container: show→seasons, season→episodes, artist→albums, album→tracks, collection→items. Set playlist=true when ratingKey is a playlist. Paginated. detail=true caps limit at 25 and adds fields such as directors, actors, collections and summaries.

NameRequiredDescriptionDefault
limitNo
detailNo
fieldsNoproject each record to these keys (ratingKey always kept). Use to keep large listings small
offsetNo
playlistNo
ratingKeyYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only/idempotent/non-destructive, so the description focuses on operation-specific traits: pagination, the detail=true interaction that caps limit at 25, and the extra fields that mode injects. It doesn't state the default page size or how ordering works, but the important behavioral rules are disclosed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three dense sentences, hierarchy mapping first, then the conditional flags, then the pagination constraint. No filler sentences and no repetition of structured fields beyond what adds meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a six-parameter paginated list tool with no output schema, the description supplies hierarchy scope, pagination, the detail cap, and playlist gating. What's missing is only the default page size and any hint of returned record shape or ordering.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With only 17% schema coverage, the description must carry the load, and it covers four of six parameters: detail (semantics plus the 25-item cap), playlist (the exact condition that requires it), and pagination via limit/offset. limit/offset defaults and the fields projection are left mostly to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb+resource relationship and enumerates the concrete hierarchies it traverses (show→seasons, season→episodes, artist→albums, album→tracks, collection→items), which cleanly separates it from get_item, search, and get_extras.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The container-type mapping tells an agent exactly which ratingKey kinds this applies to, and the playlist=true and detail=true notes give conditional guidance. It stops short of explicitly contrasting with get_item or search when a single known item is wanted.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_extrasGet extrasA
Read-onlyIdempotent

Extras (trailers, featurettes, behind-the-scenes, deleted scenes) for one item by ratingKey. Local extras include file paths. Plex-provided streaming trailers have no local file (paths is empty).

NameRequiredDescriptionDefault
ratingKeyYes

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only, idempotent, non-destructive, closed-world behavior, so the bar is lower. The description adds genuinely non-obvious behavior: local extras carry file paths while Plex streaming trailers return an empty paths value, which prevents misreading results as missing data.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences with no filler; the contents enumeration comes first and the paths caveat follows logically. Every clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description sensibly discloses the one result nuance an agent would trip over (empty paths for streaming trailers). It is adequate for a single-parameter read tool, though a note on where ratingKey comes from would close the last gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The single parameter has 0% schema description coverage, so the description carries the burden. It tells the agent the key identifies one item, but does not explain what a ratingKey is or where to obtain it (e.g., from list_media or get_item), leaving a real gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names the resource precisely and enumerates its contents (trailers, featurettes, behind-the-scenes, deleted scenes), which makes it unmistakable versus siblings like get_children or get_item. It does not explicitly contrast itself with a sibling, so it stops short of a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'for one item by ratingKey' implies you need a known item key, giving implicit context for use. But there is no explicit when-to-use guidance, no mention of how this relates to get_children or get_item, and no exclusions, so the agent must infer the routing decision.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_itemGet itemA
Read-onlyIdempotent

Full metadata for one item by ratingKey (any kind). Movies include file paths/codec/size. Shows include watch progress (episodes watched / total).

NameRequiredDescriptionDefault
ratingKeyYes

TDQS

A3.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover safety (readOnlyHint, idempotentHint, destructiveHint=false), so the description correctly spends its words on what the read returns: file paths, codec, and size for movies, and watch progress ratios for shows. That return-shape disclosure is genuinely additive given no output schema exists, though error behavior for an unknown ratingKey is unstated.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, each carrying distinct information — the first establishes scope and input, the second splits return content by item type. No filler or restatement of the title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter read tool whose annotations fully declare the safety profile, the description covers the essential return-content variation between movies and shows. The remaining gap is how a caller obtains a valid ratingKey, which is a real friction point in a 22-tool library where ids come from elsewhere.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% for the single required parameter, so the description must compensate. It confirms ratingKey identifies exactly one item and that it accepts any media kind, but never explains the key's origin or format (the schema's ^\d+$ pattern is the only format hint) or that it must be exact.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Full metadata for one item by ratingKey") and scopes it to "any kind", which helps separate it from narrow siblings like get_children, get_extras, and library_stats. It stops short of naming a sibling outright, so differentiation is inferred rather than explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no statement of when to use this versus search, list_media, get_children, or person_credits. The only implicit guidance is that the caller must already hold a ratingKey, but the description never says to obtain one via search first or what happens when an id is stale.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_watchlistWatchlistA
Read-onlyIdempotent

The server owner's plex.tv watchlist, newest first. Each entry says whether the title is in a library here (inLibrary) and gives its ratingKey if so. Titles not in any library have ratingKey null. Paginate with offset/limit.

NameRequiredDescriptionDefault
limitNo
offsetNo

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, non-destructive, closed-world, so safety is covered. The description goes beyond them by disclosing the result shape (inLibrary flag, ratingKey presence/absence) and the ordering and pagination behavior, which an agent cannot get from annotations or the empty-coverage schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, front-loaded with what the tool returns, then the notable field semantics, then pagination. No filler and nothing repeated from the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description does the work of explaining the entry fields and null-ratingKey case, which is exactly what an agent needs to interpret results. Minor gaps remain around authentication as the server owner and the page-size ceiling.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry the burden; it names both parameters and their pagination role ('paginate with offset/limit'), which is real added meaning. However it omits the default page size of 50, the maximum of 100, and that offset is measured in entries from the newest title.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource with scope: the server owner's plex.tv watchlist, newest first. The read-only framing implicitly separates it from the sibling manage_watchlist, so an agent can tell which one mutates and which one lists.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by the read-only listing nature rather than stated. It never explicitly says 'to add/remove use manage_watchlist' or defines when an agent should reach for this versus get_item or search, leaving the read/write routing to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

library_changesLibrary changesA
Read-onlyIdempotent

What changed in a library since a cutoff: 'added' (new items) and 'updated' (pre-existing items whose metadata changed since then). The buckets never overlap. Caveat: Plex bumps updatedAt during routine background refreshes, so 'updated' can include items with no meaningful change. since accepts ISO date or relative like '-7d'. kind is movie, show, season, episode, artist, album or track, and defaults to the library's own kind. Use episode in a show library to see new episodes. For a library's collections, use list_media with kind 'collection'. limit applies to each bucket. detail=true caps limit at 25 and adds fields such as directors, actors, collections and summaries.

NameRequiredDescriptionDefault
kindNo
limitNo
sinceNo-7d
detailNo
libraryNoMovies

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover safety (readOnly, idempotent, non-destructive, closed world), and the description adds non-obvious behavior an agent could not infer: Plex bumps updatedAt during background refreshes so 'updated' may contain no-op entries, limit applies per bucket, and detail=true silently caps limit at 25. That is exactly the kind of caveat that prevents misinterpretation of results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Every sentence carries distinct information: bucket definitions, the overlap guarantee, the refresh caveat, the date format, the kind enum, the episode tip, the sibling alternative, and the two limit behaviors. It is front-loaded with the core purpose and no sentence is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description must convey return shape, and it does: two named buckets plus the extra fields detail=true adds (directors, actors, collections, summaries). Combined with the annotations and the per-parameter notes, an agent has everything needed to call it correctly and interpret the response.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry the load, and it does for four of five parameters: since accepts ISO or relative like '-7d', kind enumerates values and defaults to the library's own kind, limit is per-bucket, detail caps limit and adds fields. Only the 'library' parameter's default ('Movies') and limit's max of 200 are left to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb-and-resource combination ('what changed in a library since a cutoff') and immediately defines the two returned buckets ('added' vs 'updated') with the guarantee that they never overlap. This is enough for an agent to distinguish it from siblings like list_media or library_stats without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete usage guidance: 'Use episode in a show library to see new episodes' and routes collection lookups to list_media with kind 'collection'. It lacks explicit when-not-to-use guidance versus siblings such as library_stats or watch_history, but the positive routing is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

library_statsLibrary statsA
Read-onlyIdempotent

Aggregate stats for a library: totals, watched AND unwatched counts, genre/decade/contentRating breakdowns. includeUserStats=true adds per-user play counts from watch history (recent 10,000 plays).

NameRequiredDescriptionDefault
userNosee the library as this Plex Home user (name or id)
libraryNoMovies
includeUserStatsNo

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), so the bar is lower. The description still adds real behavioral context: enabling includeUserStats derives counts from watch history capped at the recent 10,000 plays, which is a data-scope caveat an agent could not get from the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, both front-loaded: the aggregate payload first, then the optional flag's effect and its data caveat. No filler and nothing repeated from the schema or annotations.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description correctly enumerates what the response contains (totals, watched/unwatched, genre/decade/contentRating breakdowns) and explains the one non-obvious parameter. It falls short only on the 'user' and 'library' parameters, which remain unexplained.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is only 33%: 'user' has a description, but 'library' and 'includeUserStats' have none. The description compensates well for includeUserStats (per-user play counts, 10,000-play window) but says nothing about the 'user' impersonation semantics or the 'Movies' default for 'library'.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Aggregate stats for a library') and enumerates the exact aggregates returned (totals, watched/unwatched counts, genre/decade/contentRating breakdowns). This clearly separates it from listing siblings like list_libraries and list_media, though no sibling is named explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by the word 'Aggregate' and the includeUserStats note, which tells the agent when to flip that flag. There is no explicit 'use this instead of X' routing or statement of when not to use it, leaving the agent to infer that this is the summary tool versus the enumeration tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_librariesList librariesA
Read-onlyIdempotent

List all Plex library sections. libraryType is the type as Plex names it in the UI (Movies, TV Shows, Music, Photos, Other Videos) and is the field to read when deciding how to treat a library. Do NOT infer that from type: type is the underlying scanner type, and an Other Videos library (home videos, concerts, workout clips, YouTube rips) reports type 'movie' exactly like a real Movies library. agent/scanner are the raw fields libraryType is derived from. Also returns key and whether a scan is running (refreshing).

NameRequiredDescriptionDefault

No parameters

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already establish readOnly, idempotent, non-destructive, and closed-world behavior, so the description appropriately focuses on return-value semantics. It adds genuinely useful context annotations cannot provide: that each section includes a key, whether a scan is running (refreshing), and a warning about misleading type values. It does not, however, discuss ordering or performance.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four sentences are tightly packed with useful distinctions (libraryType vs type vs agent/scanner), and the core purpose leads. The agent/scanner aside is somewhat marginal, but it clarifies the source of the derived field rather than repeating the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema and no parameters, so the description must supply the shape of the result on its own. It names the key fields (libraryType, key, refreshing) and explains the most dangerous ambiguity (movie scanner vs Other Videos). Only minor omissions such as section titles or ordering keep it below a 5.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so parameter semantics has no surface to cover. Rather than adding value here, the description spends its words on response fields, which is the baseline expected when no parameters exist.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence gives a specific verb and resource: 'List all Plex library sections.' It also defines the meaning of the returned libraryType field, so an agent knows exactly what the tool enumerates. It stops short of contrasting itself with the sibling library_stats, so a 4 rather than a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description says how to interpret the output ('libraryType is the field to read when deciding how to treat a library'), but it never says when to call this tool instead of library_stats, list_media, or search. Usage is implied by the broad enumeration rather than stated through explicit alternatives or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_mediaList mediaA
Read-onlyIdempotent

List items from any library with rich filtering. Returns watched state, view counts and last-viewed on every record. watched defaults to 'all': the full library, seen and unseen. For SHOW libraries: the watched filter means any-episode-played (watched) / has-unplayed-episodes (unwatched), while each record's watched field means fully watched. A partially-watched show matches both filters' opposites. kind defaults to the library's natural top-level type (movie/show/artist). Use episode/track/album/season for deeper types, or collection to list a section's collections. rated filters on the owner's star rating (userRating, 1-10 internal scale). Movie records carry userRating, and sort=userRating asc puts unrated first. Filters combine (AND). Paginate with offset/limit. detail=true caps limit at 25 and adds fields such as directors, actors, collections and summaries. kind=collection lists a section's collections only. Other filters/sort are ignored for collections.

NameRequiredDescriptionDefault
kindNo
sortNoaddedAt
userNosee the library as this Plex Home user (name or id)
yearNo
genreNo
limitNo
orderNodesc
ratedNoall
decadeNoe.g. '1970s'
detailNo
fieldsNoproject each record to these keys (ratingKey always kept). Use to keep large listings small
offsetNo
studioNo
countryNo
libraryNoMovies
watchedNoall
addedSinceNoISO date or relative like '-30d'
collectionNofilter to members of a collection
contentRatingNoe.g. 'PG-13'
maxDurationMinNo
lastViewedAfterNolast viewed after this ISO date or relative like '-30d'
lastViewedBeforeNolast viewed before this ISO date or relative like '-3y'. Never-watched items match too unless watched='watched'

TDQS

A4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare a safe read-only, idempotent profile, and the description still adds substantial non-obvious behavior: the watched filter means any-episode-played for shows while the record's watched field means fully watched, partially-watched items match both opposites, detail=true caps limit at 25, filters combine with AND, and kind=collection ignores other filters/sort. These are exactly the semantics an agent could get wrong.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Dense and long, but appropriate for a 22-parameter tool, and key defaults (watched='all', kind default) are front-loaded. Some sentences, especially the repeated mention of kind=collection, could be consolidated without losing meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a high-complexity listing tool with no output schema, the description does note what records carry (watched state, view counts, last-viewed) and warns about the detail cap. Pagination is mentioned via offset/limit. Coverage of the many remaining filters is the only real gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With only 36% schema coverage over 22 params, the description carries much of the weight, correctly defining watched, rated (internal 1-10 scale), sort=userRating putting unrated first, the detail limit cap, and collection-only mode. It leaves several filters (year, genre, decade, studio, country, maxDurationMin) unexplained beyond their names, so it is strong but not complete.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opening sentence gives a specific verb and resource ('List items from any library') plus the scope of functionality ('rich filtering'). It is clearly distinct from siblings like library_stats or get_item, though it never names those alternatives to sharpen the boundary.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains how to use the filters (kind defaults, deep types via episode/track/album/season, collection for sections) but never states when to choose this over search, library_stats, or get_children. Usage is implied by the filter guidance rather than framed against alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_usersList usersA
Read-onlyIdempotent

All Plex accounts on this server with ids. Use names or ids with watch_history's user filter.

NameRequiredDescriptionDefault

No parameters

TDQS

A3.9/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare it read-only, idempotent, non-destructive and closed-world, so safety is fully covered. The description adds that ids are returned (useful for chaining, since there is no output schema), but says nothing about ordering, paging, or whether disabled/shared accounts are included.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with what the tool returns and followed by the practical follow-up action. No filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no input schema to explain, annotations covering the safety profile, and no output schema, the description carries the burden for return shape and does note it returns accounts with ids. Minor gaps (ordering, scope of accounts) keep it from full marks.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline is 4; nothing about parameter semantics needs to be stated or could be added.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

"All Plex accounts on this server with ids" states a specific resource and its key output field, which is enough to distinguish it from the many listing siblings (list_libraries, list_media). It does not explicitly contrast with those siblings, but the resource is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The second sentence gives a concrete downstream use: feed names or ids into watch_history's user filter. That is clear chaining guidance, though it never states when not to use this tool or what to do if only names are needed.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

manage_collectionManage collectionA
Destructive

Create, extend, prune, or delete a collection in a library. create: title + library + ratingKeys (+ kind, which defaults to the kind the library holds and must match it). add_items/remove_items: collectionId + ratingKeys. delete: collectionId (removes ONLY the grouping: its items are untouched). If Plex fails partway through remove_items, the error names the keys already removed.

NameRequiredDescriptionDefault
kindNo
titleNo
actionYes
libraryNo
ratingKeysNo
collectionIdNo

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations: it discloses that delete removes ONLY the grouping and leaves items intact, that `kind` defaults to the library's kind and must match it, and that a partial failure during remove_items reports the keys already removed — a genuinely useful non-obvious recovery detail. Permissions requirements and whether create returns the new id are not covered.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the verb set, then per-action requirements, then the failure caveat. Dense but zero waste — every clause is actionable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

A destructive multi-action tool with no output schema and no param descriptions; the description covers action semantics, required inputs, and the destructive scope. The notable omission is what comes back from create/add_items (e.g., the new collectionId), which an agent would need for chained calls.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry the load, and it does: it maps every parameter to the action(s) that require it and explains the `kind` defaulting/matching rule. It does not clarify ratingKeys being numeric strings or the 500-item cap, though the schema's pattern/maxItems partly cover that.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States specific verbs (create, extend, prune, delete) against a concrete resource (a collection in a library) and enumerates the four actions the `action` enum supports. An agent can distinguish this from manage_playlist/manage_watchlist purely on the resource noun.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Each action is paired with the parameters it needs (create: title + library + ratingKeys; add_items/remove_items: collectionId + ratingKeys; delete: collectionId), which effectively tells the agent which action to pick for a given intent. It stops short of naming an alternative tool or an explicit when-not-to-use condition.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

manage_playlistManage playlistA
Destructive

Create, extend, prune, or delete a playlist. create: title + ratingKeys (+ mediaType video|audio). add_items/remove_items: playlistId + ratingKeys. delete: playlistId (deletes ONLY the playlist, never media). If Plex fails partway through remove_items, the error names the keys already removed.

NameRequiredDescriptionDefault
titleNo
actionYes
mediaTypeNovideo
playlistIdNo
ratingKeysNo

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With destructiveHint=true already declared, the description still adds real value: 'deletes ONLY the playlist, never media' bounds the blast radius of a destructive operation, and the note that a partial remove_items failure returns an error naming the keys already removed is an unusual, genuinely useful disclosure about non-atomic behavior (consistent with idempotentHint=false). It stops short of stating permission requirements or whether create needs an existing library entry.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four short clauses, front-loaded with the verb list, each mapped to an action; no filler, no restatement of the title. The telegraphic style is dense but every fragment carries new information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a four-mode mutation tool with no output schema, the description covers inputs and destructive scope well but never says what create returns. An agent needs the new playlistId to subsequently call add_items/remove_items, and that linkage is unaddressed. Adequate but with a real gap for multi-step workflows.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description carries the burden and mostly does: it assigns title+ratingKeys(+mediaType) to create, playlistId+ratingKeys to add/remove, and playlistId to delete, which supplies the conditional-requiredness the schema does not express beyond 'action'. It omits the 500-item cap on ratingKeys and the numeric-string pattern, but those are visible in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb set and a clear resource: create/extend/prune/delete a playlist, with the four operations named to match the action enum values. It does not distinguish itself from the sibling manage_collection or manage_watchlist, so the agent gets no explicit routing signal between the three '*manage*' tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The per-action parameter mapping ('delete: playlistId') implicitly tells the agent which action to pick for which input set, and the delete clause states a clear scope. However, there is no explicit 'use this when…' or exclusion pointing at manage_collection/manage_watchlist, so the usage boundary is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

manage_watchlistManage watchlistA
Idempotent

Add movies or shows in the library to the server owner's plex.tv watchlist, or remove them, by ratingKey. Every key is checked before anything is sent: one that is not in a library, is not a movie or show, or has no Plex GUID fails the whole call. If plex.tv fails partway, the error names the keys already changed.

NameRequiredDescriptionDefault
actionYes
ratingKeysYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds substantial behavior beyond the annotations: validation is all-or-nothing ('one bad key fails the whole call') and partial plex.tv failures produce an error naming the keys already changed — exactly what an agent needs to decide whether to retry. Annotations already cover idempotency and mutation safety, and nothing in the text contradicts them.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, each front-loaded with the highest-value fact: purpose, then validation atomicity, then failure semantics. No filler and no repetition of the title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, yet the description explains failure behavior well; success-return shape (e.g. which keys were changed, count) is still unstated. For a 2-parameter mutation tool with annotations covering safety and idempotency, this is close to complete but leaves the success response to inference.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the burden and largely does: it defines ratingKey's meaning and validity preconditions (in a library, movie/show, has a Plex GUID) and makes the add/remove action explicit. It doesn't mention the 1–100 array bound or the numeric-string format, which remain schema-only.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb pair (add/remove) and resource (server owner's plex.tv watchlist), plus the addressing mechanism (ratingKey). It is clearly the mutation counterpart to the read-only sibling get_watchlist, so an agent can route between them without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The scope constraint 'in the library' and the ownership constraint ('server owner's' watchlist) make the applicable context clear. It doesn't explicitly name get_watchlist as the read alternative or state when not to use it, so exclusions are left implicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

now_playingNow playingA
Read-onlyIdempotent

What is playing on the server right now: user, item, player, progress.

NameRequiredDescriptionDefault

No parameters

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true and destructiveHint=false, so the safety profile is covered. The description adds useful behavioral context by stating the tool reports live playback state and enumerating the returned dimensions (user, item, player, progress), which the annotations cannot convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single short sentence with zero filler, front-loading the temporal qualifier 'right now' and then the returned fields. Every element earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description usefully sketches the return shape (user, item, player, progress) and the zero-parameter signature means there is little else to explain. A note on what is returned when nothing is playing would close the remaining gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing for the description to disambiguate; baseline is 4. The enumerated return fields add a little orientation but are not parameter semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific resource and temporal scope: what is playing on the server right now, plus the fields returned (user, item, player, progress). This clearly separates it from history-oriented siblings like continue_watching and watch_history, though it never names those siblings explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied by the phrase 'right now', which suggests a live snapshot versus historical queries. There is no explicit statement of when to call this instead of continue_watching or watch_history, and no prerequisites or exclusions are given.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

person_creditsPerson creditsA
Read-onlyIdempotent

Everything by a person (director, actor, writer, or composer) within one library, with optional watched filter. 'Scorsese films I haven't seen' = role:director, watched:'unwatched'. Paginated. detail=true caps limit at 25 and adds fields such as directors, actors, collections and summaries.

NameRequiredDescriptionDefault
nameYes
roleNodirector
sortNoyear
limitNo
orderNoasc
detailNo
fieldsNoproject each record to these keys (ratingKey always kept). Use to keep large listings small
offsetNo
libraryNoMovies
watchedNoall

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), and the description adds real behavioral facts beyond them: results are paginated, and detail=true imposes a hard limit of 25 while enriching records with directors/actors/collections/summaries. It does not say what happens when detail=true is combined with a limit above 25, nor the default result ordering.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four short sentences, front-loaded with the resource scope, followed by the usage example and then the operational caveats. Nothing is redundant and nothing is buried.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With 10 parameters, 10% schema coverage, and no output schema, the description supplies the key invocation facts: role, watched, pagination, and detail-mode limits. Gaps remain around sort/order/fields projection and the default return shape, but the essentials for a correct call are present.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is only 10%, so the description must carry the load. It does explain role, watched, and the detail/limit interaction well, but leaves sort, order, offset, fields, library (beyond the passing phrase 'within one library'), and the name parameter to the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific resource with scope: everything credited to a named person, restricted to one library and one role. That is clearly separable from list_media or search in substance, but no sibling tool is named, so the differentiation is implicit rather than explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The concrete example ('Scorsese films I haven't seen' = role:director, watched:'unwatched') shows exactly when this tool is the right call and how to express a natural-language request. It stops short of stating exclusions or naming the alternative (e.g. search) for across-library queries.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

random_pickRandom pickA
Read-onlyIdempotent

N random items matching the same filters as list_media (the 'just pick something' tool). Defaults to 3 picks. Combine watched=unwatched with genre, decade, maxDurationMin etc. user picks as a Plex Home user, so unwatched means unwatched by them. Each pick is the record list_media returns (title, year, watched state, genres, duration and so on). detail=true adds fields such as directors, actors, collections and summaries so you can explain the pick.

NameRequiredDescriptionDefault
kindNo
userNosee the library as this Plex Home user (name or id)
yearNo
countNo
genreNo
ratedNoall
decadeNoe.g. '1970s'
detailNo
studioNo
countryNo
libraryNoMovies
watchedNoall
addedSinceNoISO date or relative like '-30d'
collectionNofilter to members of a collection
contentRatingNoe.g. 'PG-13'
maxDurationMinNo
lastViewedAfterNolast viewed after this ISO date or relative like '-30d'
lastViewedBeforeNolast viewed before this ISO date or relative like '-3y'. Never-watched items match too unless watched='watched'

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare read-only/non-destructive/idempotent, and the description adds genuinely new behavioral context: results are scoped to the Plex Home user so 'unwatched' is per-user, each pick is the same record shape list_media returns, and detail=true enriches the record for explanation. It does not cover edge behavior (e.g. what happens when fewer than count items match), which keeps it at 4.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core identity and the default pick count, then progressively more detail. Dense and largely waste-free, though the mid-sentence 'and so on' and the trailing detail clause make the last sentence slightly sprawling.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With 18 optional params and no output schema, the description does the right work: it describes the returned record shape and what detail adds, which an agent needs for follow-up reasoning. The remaining gap is behavior under sparse results (empty or short result sets) and the count cap, neither of which is mentioned.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is low at 39% across 18 params, so the description has to compensate. It does add meaning for the important ones — count default of 3, the user-scoping of 'watched', detail's enrichment, and the combinability of genre/decade/maxDurationMin — but leaves rated, studio, country, library, addedSince, collection, contentRating and the lastViewed* params unaddressed. Partial compensation only.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('N random items') plus the key qualifier that it uses the same filters as list_media, and labels itself as the 'just pick something' tool. An agent can immediately distinguish this sampling tool from the exhaustive list_media sibling without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It establishes the use case (pick something at random) and points at list_media as the filter reference, plus a concrete recipe: 'Combine watched=unwatched with genre, decade, maxDurationMin etc.' That gives clear context. It stops short of an explicit 'do not use this when you need the full list' exclusion, so it is not a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

rate_itemRate itemB
Idempotent

Set (0-10) or clear (null) the owner's rating on an item.

NameRequiredDescriptionDefault
ratingYes
ratingKeyYes

TDQS

B3.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare the operation non-read-only, idempotent, non-destructive and closed-world, so the safety profile is covered. The description adds the useful detail that the target is the *owner's* rating (not an aggregate) and that null clears it, but says nothing about auth requirements, response behavior, or what happens if ratingKey is invalid.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence that carries the value range, the clear sentinel, and the target of the operation with zero filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple two-parameter mutation with no output schema, the description covers the rating semantics but omits what `ratingKey` is and what the call returns or does on failure. Adequate but with a clear gap on the identifier parameter.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It does explain the `rating` parameter's semantics (0-10 range, null to clear), but `ratingKey` is never explained — the schema only reveals a numeric-string pattern, not that it identifies the item. An agent is left guessing what this required key refers to.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb pair (set/clear) and resource (owner's rating on an item), with the value range and the null-clears semantics. No other rating tool exists among the siblings, so no explicit differentiation is needed, but the description also doesn't tell the agent how this relates to the many item-scoped siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: an agent can infer it should call this to assign or remove a rating, but there is no explicit when-to-use, no prerequisite (e.g. must be the owner or authenticated user), and no note about how it interacts with set_watched or other item mutations.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

scan_libraryScan libraryA

Trigger 'Scan Library Files' for one library so Plex picks up added or removed files on disk. Safe: never modifies media files or metadata edits, and never re-downloads metadata. Optional path limits the scan to one server-side folder (find valid paths via get_item file.paths). Scans run async. Poll list_libraries and watch 'refreshing'.

NameRequiredDescriptionDefault
pathNo
libraryYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=false and destructiveHint=false but say nothing about why, and the description fills that gap precisely: it never modifies media files or metadata edits and never re-downloads metadata. It also discloses two behaviors invisible in the schema – the scan is asynchronous, and completion must be observed by polling list_libraries for a 'refreshing' state.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and scope, then safety, then the optional parameter, then async/polling. Every sentence carries information, though the parenthetical path hint and the polling instruction could be tightened into one clause without losing meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter async action tool with no output schema, the description covers everything an agent needs: what it does, that it is safe despite write-like semantics, how to narrow scope, and how to verify completion. No important invocation detail is left to inference.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must carry the load, and it does for the non-obvious parameter: 'path' limits the scan to one server-side folder and points to get_item file.paths as the source of valid values. The required 'library' parameter is only implied by 'one library' – acceptable but the weaker half of the pair.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Trigger Scan Library Files') plus the resource scope ('one library') and the outcome ('Plex picks up added or removed files on disk'). No sibling tool does a scan, so the distinction is immediately clear from the name-plus-description pair.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives the triggering condition ('picks up added or removed files on disk') and routes the agent to helpers for both pre- and post-steps (get_item file.paths for valid paths, list_libraries for polling 'refreshing'). It stops short of stating when a scan is unnecessary or which siblings to choose instead, so it is strong context rather than explicit when/when-not routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

set_watchedMark watched or unwatchedA

Mark specific items watched or unwatched by ratingKey (owner account). Explicit keys only. Marking watched also records a play. If Plex fails partway, the error names the keys already marked.

NameRequiredDescriptionDefault
watchedYes
ratingKeysYes

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=false and idempotentHint=false; the description goes beyond them by explaining why writes are non-idempotent ("marking watched also records a play") and by disclosing partial-failure semantics ("the error names the keys already marked"), which is genuinely useful for retry decisions. The gap is the unwatched path: it doesn't say whether removing watched status deletes existing play history.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four short sentences, front-loaded with the action and scope, then progressively finer constraints and failure behavior. No filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter mutation with no output schema, the description covers the side effect and partial-failure reporting, which are the things an agent most needs. It stops short of stating what the successful response contains (counts vs per-key status) and of describing the unwatched side effect.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description carries the burden; it does clarify that ratingKeys must be explicit owner-account keys rather than titles or filters. It adds nothing about the numeric string format or the 1-100 item cap, and nothing about the boolean's edge behavior (e.g. marking an already-watched item).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

A specific verb pair (mark watched/unwatched) plus the resource (items addressed by ratingKey), with the account scope called out as owner. It is clearly distinct from siblings like watch_history (read) or rate_item, though it never names those siblings explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

"Explicit keys only" tells the agent it must already hold ratingKeys (e.g. from search/get_item) rather than addressing items by filter, which is useful implied guidance. However, it never states when to prefer this over rate_item, watch_history, or manage_watchlist, nor any exclusions or prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

similar_itemsSimilar itemsA
Read-onlyIdempotent

Plex's 'more like this' list for one item (movie, show, album…) by ratingKey, from your own library only. watched filters the results so 'similar but unseen' is one call.

NameRequiredDescriptionDefault
limitNo
watchedNoall
ratingKeyYes

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare read-only, idempotent, closed-world behavior, so the safety profile is covered. The description's one added behavioral fact is the scope limit ('your own library only'), plus the idea that ordering comes from Plex's own recommendation engine rather than computed similarity; it says nothing about result shape, limit behavior, or failure on an invalid/unknown ratingKey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences; the identity of the tool is front-loaded and the filtering note follows only because it adds a non-obvious capability. No filler or restatement of the name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple read tool with no output schema, the description covers what the call returns conceptually (a recommendation list) but omits the limit parameter's role and any indication of what an empty result or bad key looks like. Adequate to select the tool, thin on invocation detail.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry parameter meaning. It explains watched well (filtering for unseen items) and identifies ratingKey as the item selector, but never mentions limit (default 10, max 50), leaving one of three parameters undocumented anywhere.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb/resource: Plex's 'more like this' list for one item, addressed by ratingKey. The parenthetical (movie, show, album…) clarifies accepted item types, and 'from your own library only' separates it from a general discovery or search tool among the siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The clause 'watched filters the results so similar but unseen is one call' implies a concrete usage scenario, which is real guidance. But it never states when to prefer this over siblings like get_extras, get_children, or search, nor any prerequisites (e.g. whether the item must be in the library).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

watch_historyWatch historyA
Read-onlyIdempotent

Watch/listen history across ALL users: who played what, when. Filter by user (name or account id), library, time window (since/until, ISO dates), or a specific item (ratingKey: answers 'has X seen Y?'). Sorted newest first. kind filters the returned page client-side (movie/episode/track). total reflects the unfiltered count.

NameRequiredDescriptionDefault
kindNo
userNo
limitNo
sinceNo
untilNo
offsetNo
libraryNo
ratingKeyNolimit history to one item

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), and the description adds non-obvious behavior: default sort is newest first, kind is applied client-side to the returned page, and total is the unfiltered count. Those two quirks are genuinely important because they make filtered results look inconsistent with totals; only permission/auth context is absent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four tight sentences, each carrying distinct information: scope first, then filters, then sort order, then the two counting/filtering caveats. No restatement of the tool name or filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an 8-parameter tool with no output schema and thin schema descriptions, the definition covers scope, filtering semantics, sort order, and result-count behavior well. The remaining gap is how limit/offset interact with the client-side kind filter and how to page through the unfiltered set.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With schema description coverage at 13%, the description carries most of the load and does: user accepts a name or account id, since/until are ISO dates, ratingKey means 'has X seen Y', kind is one of movie/episode/track. limit and offset are left unexplained, though their schema defaults, bounds, and names make their meaning mostly inferable.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ("Watch/listen history") with explicit scope ("across ALL users") and an answer shape ("who played what, when"). The all-users framing implicitly separates it from per-user/session siblings like continue_watching and now_playing, and the "has X seen Y?" framing pins down the question it answers.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete invocation contexts (filter by user, library, time window, or specific item) and a named use case — answering 'has X seen Y?' via ratingKey. It stops short of naming alternative tools or exclusions (e.g., when to prefer continue_watching instead), so it's clear context rather than full routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 22 tool updatesv0.9.0
    • First observedcontinue_watching
    • First observedget_children
    • First observedget_extras
    • First observedget_item
    • First observedget_watchlist
    • First observedlibrary_changes
    • First observedlibrary_stats
    • First observedlist_libraries
    • First observedlist_media
    • First observedlist_users
    • First observedmanage_collection
    • First observedmanage_playlist
    • First observedmanage_watchlist
    • First observednow_playing
    • First observedperson_credits
    • First observedrandom_pick
    • First observedrate_item
    • First observedscan_library
    • First observedsearch
    • First observedset_watched
    • First observedsimilar_items
    • First observedwatch_history

TDQS

A3.9/5.0

Scored across 22 tools

Disambiguation5/5

The tools have clearly distinct purposes: read-only listing/search/metadata/stat tools, watch-state mutations, and playlist/collection/watchlist management are separated by resource and action. Overlapping-sounding retrieval tools (list_media, search, random_pick, similar_items, library_changes) are differentiated by scope and filters, and read vs. manage watchlist/playlist/collection pairs are unambiguous.

Naming Consistency4/5

Almost all names use consistent snake_case and readable Plex terminology, with mutating tools generally following verb_noun patterns like set_watched, rate_item, and manage_playlist. A few query/status tools use noun phrases such as library_stats, watch_history, and now_playing, but this is predictable enough to remain clear.

Tool Count4/5

At 22 tools the surface is on the high side, but the Plex domain is broad and each tool maps to a distinct capability rather than duplicating another. It is slightly over the typical 3-15 range, yet the set is reasonably scoped.

Completeness4/5

The server covers core Plex workflows well: library/media discovery, metadata, stats, changes, watch history, now playing, watch state, ratings, playlists, collections, watchlist, and library scans. Minor gaps remain around explicit playlist listing, media metadata editing, and server/library administration, but these are not fatal for the apparent scope.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    A
    maintenance
    A Model Context Protocol server that enables AI assistants to query and manage Plex Media Server content through natural language, providing library access, viewing statistics, and media management capabilities.
    58
    200 npm
    57
    JavaScript
    MIT
  • A
    license
    B
    quality
    C
    maintenance
    Enables search, browsing, and management of Plex media libraries including movies, TV shows, and music, with support for playlists, watch history, natural language music discovery, and detailed media information retrieval.
    22
    18 npm
    5
    ISC
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables 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
  • A
    license
    Not graded
    quality
    B
    maintenance
    Lets AI assistants browse your libraries, search media, get viewing recommendations, check what's on deck, and more — all read-only against your local Plex instance.
    200 npm
    1
    MIT