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.
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/PKG-INFO +310 -119
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/README.md +309 -118
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/library.py +6 -3
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/media.py +20 -2
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/playlist.py +57 -46
- plex_mcp_server-1.3.0/modules/profiles.py +44 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/user.py +14 -6
- plex_mcp_server-1.3.0/modules/watchlist.py +223 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/plex_mcp_server.egg-info/PKG-INFO +310 -119
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/plex_mcp_server.egg-info/SOURCES.txt +7 -1
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/plex_mcp_server.py +11 -9
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/pyproject.toml +1 -1
- plex_mcp_server-1.3.0/tests/test_media_profiles.py +68 -0
- plex_mcp_server-1.3.0/tests/test_playlist_profiles.py +84 -0
- plex_mcp_server-1.3.0/tests/test_profiles.py +66 -0
- plex_mcp_server-1.3.0/tests/test_watchlist.py +136 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/LICENSE +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/__init__.py +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/auth.py +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/client.py +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/collection.py +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/server.py +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/modules/sessions.py +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/plex_mcp_server.egg-info/dependency_links.txt +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/plex_mcp_server.egg-info/entry_points.txt +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/plex_mcp_server.egg-info/requires.txt +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/plex_mcp_server.egg-info/top_level.txt +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/setup.cfg +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/tests/test_http_transport.py +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/tests/test_library_contents.py +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/tests/test_oauth_logging.py +0 -0
- {plex_mcp_server-1.2.0 → plex_mcp_server-1.3.0}/tests/test_oauth_validation.py +0 -0
- {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.
|
|
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
|
-
|
|
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
|
-
- **
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
254
|
-
| `playlist_get_contents` | List items
|
|
255
|
-
| `playlist_create` | Create a
|
|
256
|
-
| `playlist_delete` | Delete a playlist. | `playlist_title`, `playlist_id` |
|
|
257
|
-
| `playlist_add_to` | Add media items
|
|
258
|
-
| `playlist_remove_from` | Remove
|
|
259
|
-
| `playlist_edit` | Change
|
|
260
|
-
| `playlist_upload_poster` | Upload a
|
|
261
|
-
| `playlist_copy_to_user` |
|
|
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
|
|
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.
|