agentsclimarketplace

Plex media server

Skill ddnetters/homelab-agent-skills/plex-media-server

Plex Media Server API — library management, media search, playback sessions, server status, and automationFrom its SKILL.md

Install
npx -y skills add ddnetters/homelab-agent-skills --skill plex-media-server

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

4 things to look at

  • reads credentialsReads from 1 credential source: `PLEX_TOKEN`.
  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
  • runs commandsInstructs the agent to run 7 commands, including `curl -s "https://plex.tv/api/v2/user" -H "X-Plex-Token: YOUR_TOKEN" | jq .` and 6 more.
  • fetches URLsInstructs the agent to fetch 7 URLs, including https://plex.tv/api/v2/user and 6 more.

SKILL.md

7.2 KB, ~2.0k tokens by cl100k_base, as published. Nobody here has run it

Plex Media Server

Manage your Plex Media Server: scan libraries, search media, monitor playback sessions, and automate maintenance.

Authentication

All Plex API calls require an X-Plex-Token. Find yours:

  1. Open Plex Web → any media item → Get Info → View XML → check URL for X-Plex-Token=
  2. Or from the Plex claim: curl -s "https://plex.tv/api/v2/user" -H "X-Plex-Token: YOUR_TOKEN" | jq .
export PLEX_URL="http://localhost:32400"
export PLEX_TOKEN="YOUR_PLEX_TOKEN"

Server status

# Server identity
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/identity" | jq .

# Server capabilities
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/" | jq '.MediaContainer | {friendlyName, version, platform, updatedAt}'

# Server preferences
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/:/prefs" | jq '.MediaContainer.Setting[] | {id, value}'

Libraries

List libraries

curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/library/sections" | \
  jq '.MediaContainer.Directory[] | {key, title, type, agent, scanner}'

Scan library

# Scan specific library (use key from list)
curl -s -X POST -H "X-Plex-Token: $PLEX_TOKEN" \
  "$PLEX_URL/library/sections/SECTION_KEY/refresh"

# Scan all libraries
curl -s -X POST -H "X-Plex-Token: $PLEX_TOKEN" \
  "$PLEX_URL/library/sections/all/refresh"

# Force full metadata refresh
curl -s -X POST -H "X-Plex-Token: $PLEX_TOKEN" \
  "$PLEX_URL/library/sections/SECTION_KEY/refresh?force=1"

Browse library contents

# List all items in a library section
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/library/sections/SECTION_KEY/all" | \
  jq '.MediaContainer.Metadata[] | {title, year, rating, addedAt}'

# Recently added
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/library/recentlyAdded" | \
  jq '.MediaContainer.Metadata[] | {title, type, year, addedAt}'

# On deck (continue watching)
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/library/onDeck" | \
  jq '.MediaContainer.Metadata[] | {title, grandparentTitle, viewOffset, duration}'

Search

# Search across all libraries
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/hubs/search?query=inception" | \
  jq '.MediaContainer.Hub[] | {type, size, Metadata: [.Metadata[]? | {title, year, type}]}'

# Search specific library
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/library/sections/SECTION_KEY/search?query=inception" | \
  jq '.MediaContainer.Metadata[] | {title, year, summary}'

Playback sessions

Currently playing

curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/status/sessions" | \
  jq '.MediaContainer.Metadata[]? | {
    title,
    user: .User.title,
    player: .Player.title,
    state: .Player.state,
    progress: ((.viewOffset / .duration * 100) | round),
    transcoding: (.TranscodeSession != null)
  }'

Playback history

curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/status/sessions/history/all" | \
  jq '.MediaContainer.Metadata[] | {title, viewedAt, accountID}'

Media info

Get item details

curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/library/metadata/RATING_KEY" | \
  jq '.MediaContainer.Metadata[0] | {title, year, summary, rating, Media: [.Media[] | {videoResolution, bitrate, container}]}'

Get TV show seasons and episodes

# Seasons of a show
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/library/metadata/SHOW_RATING_KEY/children" | \
  jq '.MediaContainer.Metadata[] | {title, index, leafCount}'

# Episodes of a season
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/library/metadata/SEASON_RATING_KEY/children" | \
  jq '.MediaContainer.Metadata[] | {title, index, duration}'

Maintenance

Optimize database

curl -s -X PUT -H "X-Plex-Token: $PLEX_TOKEN" \
  "$PLEX_URL/library/optimize"

Clean bundles (remove orphaned data)

curl -s -X PUT -H "X-Plex-Token: $PLEX_TOKEN" \
  "$PLEX_URL/library/clean/bundles"

Empty trash for a library

curl -s -X PUT -H "X-Plex-Token: $PLEX_TOKEN" \
  "$PLEX_URL/library/sections/SECTION_KEY/emptyTrash"

Analyze media (detect intros, credits)

# Analyze all items in a library
curl -s -X POST -H "X-Plex-Token: $PLEX_TOKEN" \
  "$PLEX_URL/library/sections/SECTION_KEY/analyze"

Playback control

# Get list of available players
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
  -H "Accept: application/json" \
  "$PLEX_URL/clients" | \
  jq '.MediaContainer.Server[] | {name, address, port, machineIdentifier}'

Docker deployment

services:
  plex:
    image: lscr.io/linuxserver/plex:latest
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Amsterdam
      - VERSION=docker
      - PLEX_CLAIM=claim-XXXXX  # From https://plex.tv/claim
    ports:
      - "32400:32400"
    volumes:
      - ./plex-config:/config
      - /path/to/movies:/movies
      - /path/to/tv:/tv
    restart: unless-stopped

Webhooks

Plex can send webhooks on playback events (requires Plex Pass):

Events: media.play, media.pause, media.resume, media.stop, media.scrobble, library.new, library.on.deck

Configure in Settings → Webhooks → Add Webhook URL.

Example: ntfy notification on new media

# Receive webhook and forward to ntfy (simple Flask/Node server)
# Plex sends POST with JSON payload containing event type and metadata
# Parse and forward:
curl -H "Authorization: Bearer $NTFY_TOKEN" \
     -H "Tags: movie_camera" \
     -d "New on Plex: $TITLE ($YEAR)" \
     "https://ntfy.example.com/plex"

Integration with Radarr/Sonarr

Trigger Plex library scan after Radarr/Sonarr imports:

  • Radarr: Settings → Connect → Add → Plex Media Server → enter host, port, token
  • Sonarr: Settings → Connect → Add → Plex Media Server → same config

This automatically scans the relevant library section after new media is imported.

Troubleshooting

ProblemFix
Library not showing new filesTrigger manual scan: POST /library/sections/KEY/refresh
Remote access not workingCheck port 32400 forwarded, or use reverse proxy. Check Settings → Remote Access
Transcoding issuesCheck hardware transcoding enabled. Verify /dev/dri mapped for Intel QSV
Slow library scanRun optimize and clean/bundles. Check disk I/O
Metadata wrongForce refresh: PUT /library/metadata/KEY/refresh?force=1
"Not authorized"Token expired or wrong. Re-fetch from Plex Web UI
Database lockedStop Plex, check for com.plexapp.plugins.library.db-wal, restart

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.