Plex MCP Server
Provides tools for interacting with a Plex Media Server and plex.tv watchlist, including browsing libraries and metadata, searching media, viewing watch history across users, checking live sessions, discovering unwatched or similar items, and performing safe curation actions such as playlists, collections, ratings, watched state, and watchlist changes.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Plex MCP ServerWhich unwatched 90s thrillers under 100 minutes should I watch tonight?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
plex-mcp
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 loginIt 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-mcpClients 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 |
| All sections with |
| 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 |
| Global search across libraries incl. people, collections, playlists. |
| Full metadata + file info (paths, codecs, sizes, every version) by ratingKey. |
| Children of a show (seasons), season (episodes), artist (albums), album (tracks), collection (items) or playlist (items, with |
| Trailers, featurettes, behind-the-scenes for an item. |
| Filmography by director/actor/writer/composer, with watched filter. Paginated ( |
| What's new or updated since a cutoff ( |
| Totals watched and unwatched, with genre/decade/rating breakdowns and optional per-user play counts. |
Discover
tool | what it does |
| Plex's "more like this" for an item, with a watched filter. |
| N random items matching any |
| 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. |
| The server owner's plex.tv watchlist, with whether each title is in a library here and its ratingKey if so. Paginated ( |
There are also two MCP prompts, movie_night and whats_new, and a
plex://libraries resource.
History & household
tool | what it does |
| Who watched what, and when. Filter by user, library, time window, or item. |
| 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 |
| Mark items watched/unwatched (explicit ratingKeys only). |
| Set or clear the server owner's star rating. |
| Create, add, remove, delete. Deleting removes only the grouping, never media. |
| Add movies and shows in the library to the server owner's plex.tv watchlist, or remove them (explicit ratingKeys only). |
| 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
kindthat 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=1removes every write. The curation writes and the playback tools are not registered, whateverPLEX_ALLOW_PLAYBACKsays, 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 than1,0or 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 withPLEX_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/sectionswhenPLEX_URLpoints 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 passwordTo 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:latestThe 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 |
| yes | e.g. |
| yes | owner/admin |
| no |
|
| no | HTTP listen port, an integer from 0 to 65535 (default 8627) |
| no | HTTP listen address (default |
| http mode | capability-URL secret, required in http mode (at least 16 characters of letters, digits, |
| no |
|
| no |
|
| no |
|
| no | registers the Tautulli tools when both are set (one alone is refused at startup, as is a |
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 signatureschecks the registry signatures and the provenance attestation.Image.
gh attestation verify oci://ghcr.io/dustinhansen/plex-mcp:vX.Y.Z --repo dustinhansen/plex-mcpchecks 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 --offlineand started over stdio, on Node 22 (theenginesfloor) and Node 24 (an LTS line newer than the floor).container-smoke: the image built forlinux/amd64andlinux/arm64, each on a runner of that architecture with no emulation, then booted against a local Plex fixture.server-json:server.jsonchecked by the MCP registry's validate endpoint withmcp-publisher validate, the same pinned releaserelease.ymlpublishes 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 | image | MCP registry |
|
|
|
|
|
|
| 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 toolscontinue_watchingContinue watchingARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| user | No | see the library as this Plex Home user (name or id) | |
| limit | No | ||
| detail | No | ||
| library | No |
TDQS
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.
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.
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.
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.
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.
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 childrenARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| detail | No | ||
| fields | No | project each record to these keys (ratingKey always kept). Use to keep large listings small | |
| offset | No | ||
| playlist | No | ||
| ratingKey | Yes |
TDQS
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.
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.
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.
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.
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.
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 extrasARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| ratingKey | Yes |
TDQS
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.
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.
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.
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.
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.
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 itemARead-onlyIdempotent
Full metadata for one item by ratingKey (any kind). Movies include file paths/codec/size. Shows include watch progress (episodes watched / total).
| Name | Required | Description | Default |
|---|---|---|---|
| ratingKey | Yes |
TDQS
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.
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.
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.
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.
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.
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_watchlistWatchlistARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| offset | No |
TDQS
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.
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.
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.
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.
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.
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 changesARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| kind | No | ||
| limit | No | ||
| since | No | -7d | |
| detail | No | ||
| library | No | Movies |
TDQS
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.
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.
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.
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.
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.
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 statsARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
| user | No | see the library as this Plex Home user (name or id) | |
| library | No | Movies | |
| includeUserStats | No |
TDQS
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.
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.
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.
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.
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.
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 librariesARead-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).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
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.
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.
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.
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.
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.
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 mediaARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| kind | No | ||
| sort | No | addedAt | |
| user | No | see the library as this Plex Home user (name or id) | |
| year | No | ||
| genre | No | ||
| limit | No | ||
| order | No | desc | |
| rated | No | all | |
| decade | No | e.g. '1970s' | |
| detail | No | ||
| fields | No | project each record to these keys (ratingKey always kept). Use to keep large listings small | |
| offset | No | ||
| studio | No | ||
| country | No | ||
| library | No | Movies | |
| watched | No | all | |
| addedSince | No | ISO date or relative like '-30d' | |
| collection | No | filter to members of a collection | |
| contentRating | No | e.g. 'PG-13' | |
| maxDurationMin | No | ||
| lastViewedAfter | No | last viewed after this ISO date or relative like '-30d' | |
| lastViewedBefore | No | last viewed before this ISO date or relative like '-3y'. Never-watched items match too unless watched='watched' |
TDQS
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.
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.
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.
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.
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.
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 usersARead-onlyIdempotent
All Plex accounts on this server with ids. Use names or ids with watch_history's user filter.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
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.
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.
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.
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.
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.
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 collectionADestructive
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.
| Name | Required | Description | Default |
|---|---|---|---|
| kind | No | ||
| title | No | ||
| action | Yes | ||
| library | No | ||
| ratingKeys | No | ||
| collectionId | No |
TDQS
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.
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.
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.
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.
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.
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 playlistADestructive
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.
| Name | Required | Description | Default |
|---|---|---|---|
| title | No | ||
| action | Yes | ||
| mediaType | No | video | |
| playlistId | No | ||
| ratingKeys | No |
TDQS
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.
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.
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.
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.
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.
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 watchlistAIdempotent
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.
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | ||
| ratingKeys | Yes |
TDQS
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.
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.
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.
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.
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.
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 playingARead-onlyIdempotent
What is playing on the server right now: user, item, player, progress.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
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.
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.
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.
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.
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.
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 creditsARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | ||
| role | No | director | |
| sort | No | year | |
| limit | No | ||
| order | No | asc | |
| detail | No | ||
| fields | No | project each record to these keys (ratingKey always kept). Use to keep large listings small | |
| offset | No | ||
| library | No | Movies | |
| watched | No | all |
TDQS
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.
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.
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.
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.
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.
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 pickARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| kind | No | ||
| user | No | see the library as this Plex Home user (name or id) | |
| year | No | ||
| count | No | ||
| genre | No | ||
| rated | No | all | |
| decade | No | e.g. '1970s' | |
| detail | No | ||
| studio | No | ||
| country | No | ||
| library | No | Movies | |
| watched | No | all | |
| addedSince | No | ISO date or relative like '-30d' | |
| collection | No | filter to members of a collection | |
| contentRating | No | e.g. 'PG-13' | |
| maxDurationMin | No | ||
| lastViewedAfter | No | last viewed after this ISO date or relative like '-30d' | |
| lastViewedBefore | No | last viewed before this ISO date or relative like '-3y'. Never-watched items match too unless watched='watched' |
TDQS
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.
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.
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.
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.
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.
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 itemBIdempotent
Set (0-10) or clear (null) the owner's rating on an item.
| Name | Required | Description | Default |
|---|---|---|---|
| rating | Yes | ||
| ratingKey | Yes |
TDQS
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.
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.
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.
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.
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.
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'.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | ||
| library | Yes |
TDQS
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.
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.
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.
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.
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.
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.
searchSearchARead-onlyIdempotent
Global search across ALL libraries (movies, shows, episodes, music, people, collections, playlists). Returns hits grouped by type. Use person hits with person_credits. Use item hits with get_item. Pass externalId instead of query to ask whether the library holds a title you have an IMDb, TMDB or TVDB id for ('imdb:tt0113277', 'tmdb:949', 'tvdb:12345'): it matches the ids Plex already holds on every movie and show library and returns the matching movies/shows, usually one item or none. It reads every movie and show library in full, so it takes a second or two on a large server.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | No | ||
| externalId | No | imdb:tt0113277, tmdb:949 or tvdb:12345. Look a title up by its id instead of a query |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only and idempotency, but the description adds real behavioral context: externalId lookups match ids Plex stores on every movie/show library and usually return one item or none, and the call reads every movie and show library in full so it costs a second or two on a large server. It does not address what happens when query and externalId are both supplied or both omitted.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and grouping are front-loaded, then routing guidance, then the externalId mode. The externalId sentence is long and comma-heavy, but every clause carries information an agent needs; no filler sentences.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only search tool with no output schema, the definition supplies scope, result shape, sibling routing, and the alternate id-lookup mode with its performance characteristic. Only the interaction between optional parameters and limit behavior is unstated, a minor gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is only 33%, and the description compensates well for externalId, explaining its format, matching basis and typical cardinality beyond the schema's one-line hint. However, 'query' matching semantics (exact, prefix, fuzzy) and 'limit' (default 20, capped at 50) are left entirely to the bare schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The definition states a specific verb and resource ('Global search across ALL libraries') and enumerates the media types searched, then adds what the output looks like ('hits grouped by type'). It also distinguishes itself from list_media/filtering siblings by making the cross-library scope explicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit routing is given for each result type: person hits go to person_credits, item hits go to get_item. It also carves out a distinct mode (externalId instead of query) and states precisely when that mode applies — when you already hold an IMDb/TMDB/TVDB id.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| watched | Yes | ||
| ratingKeys | Yes |
TDQS
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.
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.
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.
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.
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.
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 itemsARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| watched | No | all | |
| ratingKey | Yes |
TDQS
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.
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.
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.
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.
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.
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 historyARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| kind | No | ||
| user | No | ||
| limit | No | ||
| since | No | ||
| until | No | ||
| offset | No | ||
| library | No | ||
| ratingKey | No | limit history to one item |
TDQS
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.
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.
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.
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.
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.
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.
22 tool updates
v0.9.0- First observed
continue_watching - First observed
get_children - First observed
get_extras - First observed
get_item - First observed
get_watchlist - First observed
library_changes - First observed
library_stats - First observed
list_libraries - First observed
list_media - First observed
list_users - First observed
manage_collection - First observed
manage_playlist - First observed
manage_watchlist - First observed
now_playing - First observed
person_credits - First observed
random_pick - First observed
rate_item - First observed
scan_library - First observed
search - First observed
set_watched - First observed
similar_items - First observed
watch_history
TDQS
Scored across 22 tools
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.
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.
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.
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
Related MCP Connectors
The media memory layer for AI agents and their humans. Your AI client can search your collection, add and log what you've finished, update ratings and lists, and find patterns across everything you've read, watched, played, and listened to.
Query your Twitch streams, events, supporters, raids & rankings from an AI assistant via OAuth.
Connect AI assistants to authorized Windows and macOS devices for files, commands, and management.
Remote streamable-HTTP MCP server running on a single Cloudflare Worker. Your assistant gets live Airbnb, Amazon, Booking.com, Google Flights, Maps and Reddit data, social search on X, Instagram and TikTok, the Meta Ad Library, and image/video generation without any keys. Connect your own accounts to let it send WhatsApp or Telegram messages, work an IMAP inbox, manage Meta Ads campaigns and publish to X and LinkedIn. OAuth 2.1 with PKCE; stored credentials are AES-256-GCM encrypted.
Related MCP Servers
- AlicenseBqualityAmaintenanceA 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.58200 npm57JavaScriptMIT
- AlicenseBqualityCmaintenanceEnables 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.2218 npm5ISC
- AlicenseNot gradedqualityDmaintenanceEnables users to manage and control their Plex media library through natural language commands in MCP-compatible AI clients. It supports searching content, managing playlists, tracking library statistics, and monitoring live viewing sessions.MIT
- AlicenseNot gradedqualityBmaintenanceLets 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 npm1MIT