plex-mcp-server 1.2.0__tar.gz → 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/PKG-INFO +310 -119
  2. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/README.md +309 -118
  3. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/library.py +6 -3
  4. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/media.py +20 -2
  5. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/playlist.py +57 -46
  6. plex_mcp_server-1.3.0/modules/profiles.py +44 -0
  7. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/user.py +14 -6
  8. plex_mcp_server-1.3.0/modules/watchlist.py +223 -0
  9. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/plex_mcp_server.egg-info/PKG-INFO +310 -119
  10. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/plex_mcp_server.egg-info/SOURCES.txt +7 -1
  11. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/plex_mcp_server.py +11 -9
  12. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/pyproject.toml +1 -1
  13. plex_mcp_server-1.3.0/tests/test_media_profiles.py +68 -0
  14. plex_mcp_server-1.3.0/tests/test_playlist_profiles.py +84 -0
  15. plex_mcp_server-1.3.0/tests/test_profiles.py +66 -0
  16. plex_mcp_server-1.3.0/tests/test_watchlist.py +136 -0
  17. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/LICENSE +0 -0
  18. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/__init__.py +0 -0
  19. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/auth.py +0 -0
  20. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/client.py +0 -0
  21. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/collection.py +0 -0
  22. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/server.py +0 -0
  23. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/sessions.py +0 -0
  24. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/plex_mcp_server.egg-info/dependency_links.txt +0 -0
  25. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/plex_mcp_server.egg-info/entry_points.txt +0 -0
  26. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/plex_mcp_server.egg-info/requires.txt +0 -0
  27. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/plex_mcp_server.egg-info/top_level.txt +0 -0
  28. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/setup.cfg +0 -0
  29. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/tests/test_http_transport.py +0 -0
  30. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/tests/test_library_contents.py +0 -0
  31. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/tests/test_oauth_logging.py +0 -0
  32. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/tests/test_oauth_validation.py +0 -0
  33. {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/watcher.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plex-mcp-server
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: MCP server for Plex Media Server - Control your Plex server via Claude Desktop, Cursor, or any MCP-compatible client
5
5
  Author-email: Vladimir Tutin <patrick.d.coy@gmail.com>
6
6
  License-Expression: MIT
@@ -35,11 +35,11 @@ Dynamic: license-file
35
35
 
36
36
  # Plex MCP Server
37
37
 
38
- A powerful Model-Context-Protocol (MCP) server for interacting with Plex Media Server. It provides a standardized JSON-based interface for automation, AI agents (like Claude), and custom integrations.
38
+ An MCP server for managing Plex libraries, media, and playback from Claude, ChatGPT, and other MCP clients.
39
39
 
40
40
  ## Features
41
41
 
42
- - **Standardized API**: Unified JSON responses for all Plex operations.
42
+ - **JSON responses**: Structured results for Plex operations.
43
43
  - **Multiple Transports**: Supports `stdio`, SSE (Server-Sent Events), and Streamable HTTP.
44
44
  - **Comprehensive Control**: Manage libraries, media, collections, playlists, clients, and users.
45
45
  - **Remote Ready**: Built-in OAuth 2.1 support for integration with remote AI platforms like Claude.ai.
@@ -68,8 +68,164 @@ cd plex-mcp-server
68
68
  pip install -e .
69
69
  ```
70
70
 
71
+ ### Option 4: Docker
72
+
73
+ The published image is `ghcr.io/vladimir-tutin/plex-mcp-server:latest` for Linux
74
+ amd64 and arm64. It runs as a non-root user and serves Streamable HTTP at `/mcp`
75
+ and legacy SSE at `/sse`. No media folders need to be mounted: it uses Plex's API.
76
+
77
+ Choose [Docker Compose](#docker-compose) or [Unraid](#unraid), then follow
78
+ [Public HTTPS access](#public-https-access) for hosted clients such as Claude.ai or ChatGPT.
79
+
80
+ #### Docker Compose
81
+
82
+ ```bash
83
+ git clone https://github.com/vladimir-tutin/plex-mcp-server.git
84
+ cd plex-mcp-server
85
+ cp .env.docker.example .env
86
+ ```
87
+
88
+ Edit `.env` before starting. For a trusted LAN deployment, for example:
89
+
90
+ ```env
91
+ PLEX_URL=http://192.168.1.10:32400
92
+ PLEX_TOKEN=your-plex-token
93
+ MCP_BIND_ADDRESS=192.168.1.20
94
+ MCP_PUBLISHED_PORT=3001
95
+ MCP_SERVER_URL=http://192.168.1.20:3001
96
+ MCP_OAUTH_ENABLED=false
97
+ ```
98
+
99
+ Replace `192.168.1.10` with the Plex address and `192.168.1.20` with the Docker
100
+ host's address. Without OAuth, anyone who can reach this port can use the tools.
101
+ The supplied example file defaults to loopback (`127.0.0.1`) instead, for access
102
+ from the Docker host only. Keep `.env` private.
103
+
104
+ ```bash
105
+ docker compose pull
106
+ docker compose up -d --no-build
107
+ docker compose logs -f
108
+ ```
109
+
110
+ The LAN example's client URL is `http://192.168.1.20:3001/mcp`. Hosted clients
111
+ need a reachable HTTPS endpoint; follow [Public HTTPS access](#public-https-access).
112
+
113
+ #### Unraid
114
+
115
+ In **Docker → Add Container**, enter:
116
+
117
+ | Field | Value |
118
+ |---|---|
119
+ | Name | `plex-mcp-server` |
120
+ | Repository | `ghcr.io/vladimir-tutin/plex-mcp-server:latest` |
121
+ | Network Type | `Bridge` |
122
+ | Privileged | Off |
123
+
124
+ Use **Add another Path, Port, Variable, Label or Device** to add a **Port**:
125
+ container port `3001`, an unused host port such as `3001`, connection type **TCP**.
126
+ Then add each of these as a **Variable**, using the exact key shown:
127
+
128
+ | Key | Example value | Purpose |
129
+ |---|---|---|
130
+ | `PLEX_URL` | `http://192.168.1.10:32400` | Plex server address reachable from the container |
131
+ | `PLEX_TOKEN` | `your-plex-token` | Plex API credential |
132
+ | `MCP_SERVER_URL` | `http://192.168.1.20:3001` | Address clients use to reach this MCP server |
133
+ | `MCP_OAUTH_ENABLED` | `false` | LAN-only example; enable OAuth for public access below |
134
+
135
+ Use your Unraid IP and chosen host port in `MCP_SERVER_URL`. The **Name** of a
136
+ variable is a display label; its **Key** must match the table. No `.env` file or
137
+ media path mapping is required when entering variables in Unraid.
138
+
139
+ Click **Apply**. For this example, connect a LAN client to
140
+ `http://192.168.1.20:3001/mcp`. The endpoint speaks MCP; it is not a browser dashboard.
141
+ See Unraid's [container configuration guide](https://docs.unraid.net/unraid-os/using-unraid-to/run-docker-containers/managing-and-customizing-containers/)
142
+ for managing variables and port mappings.
143
+
144
+ `MCP_BIND_ADDRESS` and `MCP_PUBLISHED_PORT` are settings used by this repository's
145
+ Compose file. In Unraid, configure the port mapping in the form instead; adding
146
+ those two variables does not change Unraid's published port.
147
+
148
+ #### Public HTTPS access
149
+
150
+ Setting `MCP_SERVER_URL` does **not** create DNS records, configure HTTPS, or
151
+ publish the container. Complete these steps before connecting a hosted client:
152
+
153
+ 1. Create an OAuth provider using [Remote Access & OAuth](#remote-access--oauth).
154
+ 2. Set these values in Compose's `.env`, or add them as **Variables** in Unraid:
155
+
156
+ ```env
157
+ MCP_SERVER_URL=https://plexmcp.example.com
158
+ MCP_OAUTH_ENABLED=true
159
+ MCP_OAUTH_ISSUER=https://auth.example.com/application/o/plexmcp-oauth/
160
+ MCP_OAUTH_AUDIENCE=your-oauth-client-id
161
+ ```
162
+
163
+ Keep `PLEX_URL` and `PLEX_TOKEN` set as above. These are separate from your
164
+ OAuth credentials. `MCP_SERVER_URL` is the public **base URL**, without `/mcp`
165
+ or `/sse`. `MCP_OAUTH_ISSUER` is the provider's exact issuer URL,
166
+ including its path and trailing slash.
167
+ 3. Point the public hostname at your reverse proxy and configure a valid HTTPS
168
+ certificate. Forward that hostname to the MCP container's published address,
169
+ for example `http://192.168.1.20:3001`. This is the Docker/Unraid host and
170
+ **host port**, not Plex's port `32400` or Authentik's address.
171
+ 4. Forward all paths on that hostname unchanged, preserve the public `Host` and
172
+ `Authorization` headers, and allow streaming responses without buffering.
173
+ With Compose, the published address must be reachable by the proxy: use the
174
+ Docker host's LAN address when the proxy runs elsewhere. A proxy in another
175
+ container cannot reach this container through its own `127.0.0.1`.
176
+ 5. Let requests reach the MCP server's OAuth middleware rather than intercepting
177
+ them with a browser-login page at the proxy. This includes `/mcp`, `/sse`,
178
+ `/messages/`, `/.well-known/` discovery paths, `/authorize`, and `/token`.
179
+ The MCP server validates bearer tokens; Authentik handles the OAuth login.
180
+ 6. Apply the Unraid changes, or run `docker compose up -d --no-build` after editing
181
+ `.env`. Configure clients with `https://plexmcp.example.com/mcp` (Streamable HTTP)
182
+ or `https://plexmcp.example.com/sse` (legacy SSE).
183
+
184
+ With OAuth enabled, an unauthenticated request to `/mcp` or `/sse` should return
185
+ **401**, while `/.well-known/oauth-protected-resource` should return JSON. A proxy
186
+ login page or a 502 indicates the request is not reaching the expected MCP handler.
187
+ Finish by signing in from your client and asking it to list your Plex libraries.
188
+
189
+ When moving an existing deployment, update the reverse proxy's upstream to the
190
+ new host and port. You can retain the public MCP URL and client URLs. If the OAuth
191
+ provider also moves, preserve its issuer URL and configuration or update
192
+ `MCP_OAUTH_ISSUER` and the client credentials to match.
193
+
194
+ #### Updating or building the image
195
+
196
+ `latest` tracks published releases; `edge` tracks main. Use a version tag such as
197
+ `1.2.0` to pin a release. Running containers do not update automatically.
198
+
199
+ For Compose, update with:
200
+
201
+ ```bash
202
+ docker compose pull
203
+ docker compose up -d --no-build
204
+ ```
205
+
206
+ To build from source, run `docker compose build --pull`, then
207
+ `docker compose up -d --no-build`.
208
+
209
+ To run the published image without Compose, prepare `.env` with the server
210
+ variables above and choose the host binding explicitly:
211
+
212
+ ```bash
213
+ docker run -d --name plex-mcp-server --restart unless-stopped --init \
214
+ --env-file .env -p 127.0.0.1:3001:3001 ghcr.io/vladimir-tutin/plex-mcp-server:latest
215
+ ```
216
+
217
+ This command publishes on loopback only. For standalone SSE, append
218
+ `--transport sse --host 0.0.0.0 --port 3001`. For stdio, use
219
+ `docker run --rm -i --env-file .env ghcr.io/vladimir-tutin/plex-mcp-server:latest --transport stdio`
220
+ without publishing a port.
221
+
71
222
  ## Configuration
72
223
 
224
+ For containers, use the [Docker setup](#option-4-docker). The following options also
225
+ apply to direct Python installations. `PLEX_URL` points to Plex; `MCP_SERVER_URL`
226
+ is the address of this MCP service. Inside a container, `localhost` refers to the
227
+ container itself, so use a reachable network address for Plex.
228
+
73
229
  Set your Plex server URL and Token using one of these methods:
74
230
 
75
231
  ### 1. Command Line Arguments
@@ -84,15 +240,7 @@ PLEX_URL=http://localhost:32400
84
240
  PLEX_TOKEN=your-authentication-token
85
241
  MCP_OAUTH_ENABLED=false
86
242
  ```
87
- or with OAuth Enabled
88
- ```env
89
- PLEX_URL=http://localhost:32400
90
- PLEX_TOKEN=your-authentication-token
91
- MCP_OAUTH_ENABLED=true
92
- MCP_OAUTH_ISSUER=https://auth.example.com/application/o/plexmcp-oauth/
93
- MCP_SERVER_URL=https://plexmcp.example.com
94
- MCP_OAUTH_AUDIENCE=your-oauth-client-id # optional, recommended
95
- ```
243
+ For OAuth settings, see [Remote Access & OAuth](#remote-access--oauth).
96
244
 
97
245
  ### 3. MCP Client Config
98
246
  Example for Claude Desktop (`%APPDATA%/Claude/claude_desktop_config.json`):
@@ -173,15 +321,108 @@ Register the exact ChatGPT callback URL with your OAuth provider. It can look li
173
321
  connection, not the literal placeholder. Create the plugin, complete sign-in, and
174
322
  refresh its tools. Test it by asking ChatGPT to list your Plex libraries.
175
323
 
176
- If a reverse proxy uses browser-based login, allow the MCP endpoints (`/mcp`, plus
177
- `/sse` and `/messages/` if used), discovery endpoints, `/authorize`, and `/token`
178
- through to the MCP server. With server OAuth enabled, the MCP server itself validates
179
- the bearer token. See [Remote Access & OAuth](#remote-access--oauth) for the full setup.
324
+ For reverse-proxy routing, see [Public HTTPS access](#public-https-access).
180
325
 
181
326
  The website plugin and a local Codex MCP entry are separate connections. For local
182
327
  Codex OAuth configuration, see the [client setup instructions](#3-configure-each-client).
183
328
 
184
329
 
330
+ ## Streamable HTTP
331
+
332
+ Start the server with `--transport streamable-http` to expose `/mcp`:
333
+
334
+ ```bash
335
+ plex-mcp-server --transport streamable-http --port 3001 --server-url https://plexmcp.example.com
336
+ ```
337
+
338
+ Configure your client with `https://plexmcp.example.com/mcp`. This mode also keeps
339
+ the legacy `/sse` and `/messages/` endpoints available for existing clients.
340
+ The default transport remains SSE; `--transport stdio` is also supported.
341
+
342
+ OAuth is optional on both HTTP transports. Enable it with `--oauth-enabled` or
343
+ `MCP_OAUTH_ENABLED=true`, using the issuer settings below. Without OAuth, anyone
344
+ who can reach the endpoints can call the tools; use an appropriate network boundary.
345
+
346
+ Both SSE and Streamable HTTP validate Host and Origin headers to block DNS-rebinding
347
+ attacks (a malicious web page driving your server through your browser). `--server-url`
348
+ (or `MCP_SERVER_URL`) permits your public hostname and origin even with OAuth disabled.
349
+ For additional proxy names, LAN addresses, or browser origins, set comma-separated
350
+ `MCP_ALLOWED_HOSTS` and `MCP_ALLOWED_ORIGINS` (e.g. `MCP_ALLOWED_HOSTS=192.168.1.20:3001`).
351
+ Loopback hosts are allowed by default. If a remote client gets `421 Invalid Host header`,
352
+ add its host and port to `MCP_ALLOWED_HOSTS`. For a rejected browser origin,
353
+ add the full origin (scheme, host, and port) to `MCP_ALLOWED_ORIGINS`.
354
+
355
+ ## Remote Access & OAuth
356
+
357
+ OAuth protects both HTTP transports. The following setup uses Authentik as the identity provider.
358
+
359
+ ### How it works
360
+ The MCP server acts as the OAuth front door: clients discover it via
361
+ `/.well-known/oauth-protected-resource`, are sent to `/authorize` (which redirects to
362
+ your identity provider), and exchange codes at `/token` (which the server proxies to the
363
+ provider). Clients therefore only ever need **your MCP server URL** plus the provider's
364
+ Client ID / Secret. The browser visits the provider to complete sign-in. Access tokens are JWTs
365
+ validated against the provider's JWKS; the server checks signature, expiry, issuer, and
366
+ (optionally) audience on every request, on both SSE and Streamable HTTP.
367
+
368
+ ### 1. Create an OAuth provider (Authentik example)
369
+ 1. **Providers → Create → OAuth2/OpenID Provider.** Client type *Confidential*, signing
370
+ key set (RS256). Note the **Client ID** and **Client Secret**.
371
+ 2. **Redirect URIs** — add one entry per client you plan to use. These are the values
372
+ seen in practice:
373
+
374
+ | Client | Redirect URI |
375
+ |---|---|
376
+ | Claude.ai / Claude Desktop | `https://claude.ai/api/mcp/auth_callback` |
377
+ | ChatGPT connector | `https://chatgpt.com/connector/oauth/<connector-id>` — shown in the connector dialog |
378
+ | Codex CLI / local tools | loopback, random port: regex `^http://127\.0\.0\.1:[0-9]+/callback/<callback-id>$` |
379
+
380
+ Keep this list tight. A catch-all like `.*` lets anyone who can get you to click a
381
+ link capture an authorization code.
382
+ 3. **Applications → Create**, bind it to the provider, and **bind an access policy**
383
+ (your user or group). Any Authentik user who can authorize this application gets full
384
+ control of Plex — there are no per-user roles inside the MCP server.
385
+ 4. Copy the provider's **OpenID Configuration Issuer** URL, e.g.
386
+ `https://auth.example.com/application/o/plexmcp-oauth/` (trailing slash included).
387
+
388
+ ### 2. Configure the server
389
+
390
+ For Docker or Unraid, enter these as environment variables using the
391
+ [public HTTPS setup](#public-https-access), which also covers DNS and the reverse proxy.
392
+
393
+ ```env
394
+ MCP_OAUTH_ENABLED=true
395
+ MCP_OAUTH_ISSUER=https://auth.example.com/application/o/plexmcp-oauth/
396
+ MCP_SERVER_URL=https://plexmcp.example.com
397
+ MCP_OAUTH_AUDIENCE=<Client ID> # optional, recommended
398
+ ```
399
+ or the equivalent flags: `--oauth-enabled --oauth-issuer … --server-url … --oauth-audience …`.
400
+
401
+ - `MCP_SERVER_URL` must be the public HTTPS URL clients reach; it also becomes the
402
+ allowed Host/Origin for DNS-rebinding protection. Use the base URL without `/mcp`
403
+ or `/sse`; HTTPS is provided by your reverse proxy.
404
+ - `MCP_OAUTH_AUDIENCE` makes the server reject tokens minted for any *other* client of
405
+ the same provider. Set it to the Client ID from step 1. Leaving it unset disables audience validation for compatibility with existing deployments.
406
+
407
+ ### 3. Configure each client
408
+ Use the Client ID and Secret from step 1 and one of these URLs:
409
+
410
+ | Client | URL | Notes |
411
+ |---|---|---|
412
+ | Claude.ai / Claude Desktop | `https://plexmcp.example.com/sse` | Claude probes with a Streamable HTTP POST first and falls back to SSE automatically |
413
+ | ChatGPT | `https://plexmcp.example.com/mcp` | Authentication: OAuth, paste Client ID/Secret |
414
+ | Codex CLI | `codex mcp add plex --url https://plexmcp.example.com/mcp --oauth-client-id <id> --oauth-client-secret <secret>` | opens a browser for login |
415
+
416
+ Clients configured with `/mcp` use Streamable HTTP; `/sse` uses the legacy transport.
417
+ Both are protected identically.
418
+
419
+ ### Discovery Endpoints
420
+ When OAuth is active, the following standard endpoints are exposed without
421
+ authentication:
422
+ - `/.well-known/oauth-protected-resource`
423
+ - `/.well-known/oauth-authorization-server`
424
+ - `/authorize` (redirects to the provider) and `/token` (proxied to the provider)
425
+
185
426
  ## Command Reference
186
427
 
187
428
  ### Library Module
@@ -195,7 +436,7 @@ Tools for exploring and managing your Plex libraries.
195
436
  | `library_scan` | Scans a library for new files. | `library_name` |
196
437
  | `library_get_details` | Gets detailed information about a library. | `library_name` |
197
438
  | `library_get_recently_added` | Lists recently added items in a library. | `library_name`, `limit: int` |
198
- | `library_get_contents` | Lists current library items with media IDs, filters, and pagination. | `library_name`, `watched: bool`, `unwatched: bool`, `offset: int`, `limit: int`, `sort`, `genre`, `year`, `content_rating`, `director`, `actor`, `writer`, `resolution`, `network`, `studio` |
439
+ | `library_get_contents` | Lists current library items with media IDs, filters, and pagination. | `library_name`, `watched: bool`, `unwatched: bool`, `offset: int`, `limit: int`, `sort`, `genre`, `year`, `content_rating`, `director`, `actor`, `writer`, `resolution`, `network`, `studio`, `username` |
199
440
 
200
441
  To list watched movies currently in your library (rather than playback history):
201
442
 
@@ -238,27 +479,61 @@ Tools for searching, inspecting, and editing specific media items.
238
479
  | Command | Description | Parameters |
239
480
  |---------|-------------|------------|
240
481
  | `media_search` | Search for media across all libraries. | `query`, `library_name`, `content_type` |
241
- | `media_get_details` | Get comprehensive details for an item. | `media_title`, `library_name`, `media_id` |
482
+ | `media_get_details` | Get comprehensive details for an item. | `media_title`, `library_name`, `media_id`, `username` |
242
483
  | `media_edit_metadata` | Update tags, genres, summary, or title. | `media_title`, `library_name`, `new_title`, `new_summary`, `new_rating`, `new_release_date`, `new_genre`, `remove_genre`, `new_director`, `new_studio`, `new_tags` |
243
- | `media_delete` | Remove an item from Plex. | `media_title`, `library_name`, `media_id` |
484
+ | `media_delete` | Remove an item from Plex. | `media_title`, `library_name`, `media_id`, `username` |
244
485
  | `media_get_artwork` | Retrieve posters or background artwork. | `media_title`, `library_name`, `art_type: str` |
245
486
  | `media_set_artwork` | Set artwork from a local path or URL. | `media_title`, `library_name`, `poster_path`, `poster_url`, `background_path`, `background_url` |
246
487
  | `media_list_available_artwork` | List alternative artwork available for selection. | `media_title`, `library_name`, `art_type` |
247
488
 
489
+ `library_get_contents` and `media_get_details` accept `username` for a Home or
490
+ shared user, using the configured admin credentials. Omit it for your account.
491
+ Watched filters and saved playback progress belong to the selected user; failed
492
+ access returns an error. Movie, episode and track details include `watched`,
493
+ `view_count`, `playback_offset_ms`, `in_progress`, and `progress_percent`.
494
+
495
+ ### Watchlist Module
496
+
497
+ Manage the Plex account Watchlist, including movies and shows outside your library.
498
+
499
+ | Command | Description | Parameters |
500
+ |---|---|---|
501
+ | `watchlist_list` | List Watchlist titles. | `offset`, `limit` (1–100) |
502
+ | `watchlist_search` | Search Plex Discover for titles to add. | `query`, `content_type` (movie/show), `limit` (1–100) |
503
+ | `watchlist_add` | Add a movie or show. | `guid` |
504
+ | `watchlist_remove` | Remove a movie or show. | `guid` |
505
+
506
+ Search first, then pass the chosen result's `guid` to `watchlist_add`. To remove
507
+ an entry, use its GUID from `watchlist_list`. These `plex://movie/...` or
508
+ `plex://show/...` identifiers are cloud IDs, not local `media_id`/`ratingKey` values.
509
+ List results default to 50 items; increase `offset` for the next page.
510
+
511
+ All four tools use the configured `PLEX_TOKEN`. Pass `username` to select a user
512
+ by exact username or profile title, or `home_user_id` to select by ID (takes
513
+ precedence). Supply `pin` if Plex requires it. If Plex cannot access that user's
514
+ Watchlist, the tool returns an error without falling back to your Watchlist.
515
+ Omit the selectors to use the configured account.
516
+ Add/remove operations confirm membership and leave media files and watched status unchanged.
517
+
248
518
  ### Playlist Module
249
519
  Manage your personal and shared playlists.
250
520
 
251
521
  | Command | Description | Parameters |
252
522
  |---------|-------------|------------|
253
- | `playlist_list` | List all available playlists. | None |
254
- | `playlist_get_contents` | List items contained in a playlist. | `playlist_title`, `playlist_id` |
255
- | `playlist_create` | Create a new playlist from items. | `title`, `items: List[str]` |
256
- | `playlist_delete` | Delete a playlist. | `playlist_title`, `playlist_id` |
257
- | `playlist_add_to` | Add media items to a playlist. | `playlist_title`, `items: List[str]`, `playlist_id` |
258
- | `playlist_remove_from` | Remove specific items from a playlist. | `playlist_title`, `items: List[str]`, `playlist_id` |
259
- | `playlist_edit` | Change playlist title or summary. | `playlist_title`, `new_title`, `new_summary`, `playlist_id` |
260
- | `playlist_upload_poster` | Upload a custom poster image. | `playlist_title`, `image_path`, `playlist_id` |
261
- | `playlist_copy_to_user` | Share/Copy a playlist to another user. | `playlist_title`, `username`, `playlist_id` |
523
+ | `playlist_list` | List playlists. | `library_name`, `content_type`, `username` |
524
+ | `playlist_get_contents` | List playlist items. | `playlist_title`, `playlist_id`, `username` |
525
+ | `playlist_create` | Create a playlist. | `playlist_title`, `item_titles`, `library_name`, `summary`, `username` |
526
+ | `playlist_delete` | Delete a playlist. | `playlist_title`, `playlist_id`, `username` |
527
+ | `playlist_add_to` | Add media items. | `playlist_title`, `playlist_id`, `item_titles`, `item_ids`, `username` |
528
+ | `playlist_remove_from` | Remove playlist items. | `playlist_title`, `playlist_id`, `item_titles`, `username` |
529
+ | `playlist_edit` | Change title or summary. | `playlist_title`, `playlist_id`, `new_title`, `new_summary`, `username` |
530
+ | `playlist_upload_poster` | Upload a poster. | `playlist_title`, `playlist_id`, `poster_url`, `poster_filepath`, `username` |
531
+ | `playlist_copy_to_user` | Copy a playlist to another user. | `playlist_title`, `playlist_id`, `username`, `source_username` |
532
+
533
+ Pass `username` to work with a Home or shared user's playlists using the configured
534
+ admin credentials. Omit it to use the configured account. Failed access returns an
535
+ error. For `playlist_copy_to_user`, `username` is the destination; `source_username`
536
+ selects the source account and defaults to the configured account.
262
537
 
263
538
  ### Collection Module
264
539
  Organize movies and shows into collections.
@@ -281,17 +556,22 @@ Information about the server owner and shared users.
281
556
  | `user_list_all_users` | List all users with types and IDs. | None |
282
557
  | `user_get_info` | Detailed info for a specific user. | `username` |
283
558
  | `user_get_on_deck` | Get "On Deck" items for a user. | `username` |
284
- | `user_get_continue_watching` | Get partially watched items to resume. | `limit: int` |
559
+ | `user_get_continue_watching` | Get partially watched items to resume. | `limit`, `username`, `user_id` |
285
560
  | `user_get_watch_history` | Retrieve personal watch history. | `username`, `limit`, `content_type`, `user_id` |
286
561
  | `user_get_statistics` | Watch progress and usage statistics. | `time_period`, `username` |
287
562
 
563
+ `user_get_continue_watching` accepts an exact username/profile title or a `user_id`
564
+ from `user_list_all_users`. The ID takes precedence; omit both for the configured
565
+ account. Access to other users requires the server administrator's token and is
566
+ limited to this Plex server. Failed access returns an error.
567
+
288
568
  ### Sessions Module
289
569
  Monitor real-time server activity.
290
570
 
291
571
  | Command | Description | Parameters |
292
572
  |---------|-------------|------------|
293
573
  | `sessions_get_active` | Get currently playing items and clients. | None |
294
- | `sessions_get_media_playback_history` | History for a specific media item. | `media_title`, `library_name`, `media_id` |
574
+ | `sessions_get_media_playback_history` | History for a specific media item. | `media_title`, `library_name`, `media_id`, `username` |
295
575
 
296
576
  ### Server Module
297
577
  Maintenance and administrative tools.
@@ -322,95 +602,6 @@ Control playback and navigation on Plex clients.
322
602
  | `client_navigate` | Send remote control navigation commands. | `client_name`, `command`, `client_id` |
323
603
  | `client_set_streams` | Changes audio or subtitle tracks. | `client_name`, `audio_stream_id`, `subtitle_stream_id`, `client_id` |
324
604
 
325
- ## Streamable HTTP
326
-
327
- Start the server with `--transport streamable-http` to expose `/mcp`:
328
-
329
- ```bash
330
- plex-mcp-server --transport streamable-http --port 3001 --server-url https://plexmcp.example.com
331
- ```
332
-
333
- Configure your client with `https://plexmcp.example.com/mcp`. This mode also keeps
334
- the legacy `/sse` and `/messages/` endpoints available for existing clients.
335
- The default transport remains SSE; `--transport stdio` is also supported.
336
-
337
- OAuth is optional on both HTTP transports. Enable it with `--oauth-enabled` or
338
- `MCP_OAUTH_ENABLED=true`, using the issuer settings below. Without OAuth, anyone
339
- who can reach the endpoints can call the tools; use an appropriate network boundary.
340
-
341
- Both SSE and Streamable HTTP validate Host and Origin headers to block DNS-rebinding
342
- attacks (a malicious web page driving your server through your browser). `--server-url`
343
- (or `MCP_SERVER_URL`) permits your public hostname and origin even with OAuth disabled.
344
- For additional proxy names, LAN addresses, or browser origins, set comma-separated
345
- `MCP_ALLOWED_HOSTS` and `MCP_ALLOWED_ORIGINS` (e.g. `MCP_ALLOWED_HOSTS=10.0.1.67:3002`).
346
- Loopback hosts are allowed by default. If a remote client gets `421 Invalid Host header`,
347
- add the hostname it connects with to one of these settings.
348
-
349
- ## Remote Access & OAuth
350
-
351
- The Plex MCP Server can be integrated with remote platforms like **Claude.ai** via SSE or Streamable HTTP and optional OAuth 2.1. This allows you to talk to your MCP server directly from the Claude interface from anywhere.
352
-
353
- ### How it works
354
- The MCP server acts as the OAuth front door: clients discover it via
355
- `/.well-known/oauth-protected-resource`, are sent to `/authorize` (which redirects to
356
- your identity provider), and exchange codes at `/token` (which the server proxies to the
357
- provider). Clients therefore only ever need **your MCP server URL** plus the provider's
358
- Client ID / Secret — they never talk to the provider directly. Access tokens are JWTs
359
- validated against the provider's JWKS; the server checks signature, expiry, issuer, and
360
- (optionally) audience on every request, on both SSE and Streamable HTTP.
361
-
362
- ### 1. Create an OAuth provider (Authentik example)
363
- 1. **Providers → Create → OAuth2/OpenID Provider.** Client type *Confidential*, signing
364
- key set (RS256). Note the **Client ID** and **Client Secret**.
365
- 2. **Redirect URIs** — add one entry per client you plan to use. These are the values
366
- seen in practice:
367
- | Client | Redirect URI |
368
- |---|---|
369
- | Claude.ai / Claude Desktop | `https://claude.ai/api/mcp/auth_callback` |
370
- | ChatGPT connector | `https://chatgpt.com/connector/oauth/<connector-id>` — shown in the connector dialog |
371
- | Codex CLI / local tools | loopback, random port: regex `^http://127\.0\.0\.1:[0-9]+/callback/<callback-id>$` |
372
-
373
- Keep this list tight. A catch-all like `.*` lets anyone who can get you to click a
374
- link capture an authorization code.
375
- 3. **Applications → Create**, bind it to the provider, and **bind an access policy**
376
- (your user or group). Any Authentik user who can authorize this application gets full
377
- control of Plex — there are no per-user roles inside the MCP server.
378
- 4. Copy the provider's **OpenID Configuration Issuer** URL, e.g.
379
- `https://auth.example.com/application/o/plexmcp-oauth/` (trailing slash included).
380
-
381
- ### 2. Configure the server
382
- ```env
383
- MCP_OAUTH_ENABLED=true
384
- MCP_OAUTH_ISSUER=https://auth.example.com/application/o/plexmcp-oauth/
385
- MCP_SERVER_URL=https://plexmcp.example.com
386
- MCP_OAUTH_AUDIENCE=<Client ID> # optional, recommended
387
- ```
388
- or the equivalent flags: `--oauth-enabled --oauth-issuer … --server-url … --oauth-audience …`.
389
-
390
- - `MCP_SERVER_URL` must be the public HTTPS URL clients reach; it also becomes the
391
- allowed Host/Origin for DNS-rebinding protection.
392
- - `MCP_OAUTH_AUDIENCE` makes the server reject tokens minted for any *other* client of
393
- the same provider. Set it to the Client ID from step 1. Leaving it unset disables audience validation for compatibility with existing deployments.
394
-
395
- ### 3. Configure each client
396
- Use the Client ID and Secret from step 1 and one of these URLs:
397
-
398
- | Client | URL | Notes |
399
- |---|---|---|
400
- | Claude.ai / Claude Desktop | `https://plexmcp.example.com/sse` | Claude probes with a Streamable HTTP POST first and falls back to SSE automatically |
401
- | ChatGPT | `https://plexmcp.example.com/mcp` | Authentication: OAuth, paste Client ID/Secret |
402
- | Codex CLI | `codex mcp add plex --url https://plexmcp.example.com/mcp --oauth-client-id <id> --oauth-client-secret <secret>` | opens a browser for login |
403
-
404
- Clients configured with `/mcp` use Streamable HTTP; `/sse` uses the legacy transport.
405
- Both are protected identically.
406
-
407
- ### Discovery Endpoints
408
- When OAuth is active, the following standard endpoints are exposed without
409
- authentication:
410
- - `/.well-known/oauth-protected-resource`
411
- - `/.well-known/oauth-authorization-server`
412
- - `/authorize` (redirects to the provider) and `/token` (proxied to the provider)
413
-
414
605
  ## Response Formats
415
606
 
416
607
  All tools return information in JSON format for consistent parsing.