Skip to main content
Glama
CarlDog
by CarlDog

plex-mcp

code confidence · claude-opus-4-8[1m] · 2026-07-07 · detalles

Un servidor MCP para Plex Media Server, empaquetado como un contenedor Docker. Permite que un cliente MCP (Claude Desktop, etc.) explore y busque en tus bibliotecas de Plex.

Herramientas

工具

描述

plex_list_libraries

列出服务器上的所有媒体库(分区)

plex_search

在所有媒体库中搜索

plex_hub_search

通过 Plex 的集线器搜索端点进行搜索,包括收藏集(不同于 plex_search)

plex_recently_added

最近添加的项目,可选项按分区筛选

plex_on_deck

“正在播放”(部分观看 / 下一个)的项目;可选的 section_id 将范围限定到一个媒体库分区

plex_get_item

通过评分键获取某一项目的元数据。传递 minimal=true 可丢弃庞大的演员/工作人员/图像数组(在演员阵容庞大的电影上可减少约 80% 大小),同时保留字幕轨道信息;传递 fields=[...] 进行显式投影

plex_browse

列出媒体库分区中的项目(分页,可选类型筛选,可选 collection 标题筛选,可选稀疏 fields 投影)

plex_list_collections

列出媒体库分区中的收藏集(对 plex_browse 的 collection 类型的薄包装)

plex_get_children

某项目的子项(剧季→剧集,季→剧集,艺术家→专辑)

plex_now_playing

服务器上当前播放中的会话

plex_history

播放历史记录(分页,最新的在前)

plex_mark_watched

将项目标记为已观看(可撤销)

plex_mark_unwatched

将项目标记为未观看(可撤销)

plex_rate_item

设置项目的 0-10 分用户评分;省略 rating 则清除评分并恢复为未评分

plex_list_playlists

列出所有播放列表(常规 + 智能)

plex_get_playlist_items

列出播放列表的内容

plex_create_playlist

创建以单个项目为种子的常规播放列表

plex_add_to_playlist

将项目追加到常规播放列表

plex_remove_from_playlist

通过 playlistItemID 移除项目

plex_delete_playlist

删除播放列表(仅元数据——媒体文件不受影响)

plex_hubs

Plex 策划的服务器范围中心(继续观看、最近发布等)

plex_section_hubs

限定到某一个媒体库分区的策划中心

plex_related

Plex 针对某一项目的“相关”策划中心(按来源分组)

plex_similar

某项目的算法推荐相似项目(平面列表)

plex_refresh_metadata

从当前代理重新拉取项目的元数据(可选 force)

plex_get_matches

列出项目(TMDB / TVDB 等)的候选匹配;可选标题/年份/代理/语言覆盖

plex_apply_match

将选定的匹配(guid/name)应用到项目上;覆盖代理绑定

plex_edit_metadata

覆盖标量元数据字段(标题、简介、年份等),带字段级锁定

plex_unmatch

将项目从代理绑定中分离(恢复未匹配状态);上锁的字段不受影响

plex_refresh_section

对整个媒体库分区触发元数据刷新(增量或深层)

plex_split

将 Plex 项目拆分为其组成媒体变体,作为 N 个单独项目

plex_merge_items

将其他项目合并到目标项目中(来源被并入;目标项目保留)

plex_get_image

获取某个项目的海报/封面/背景图/透明图字节,作为 MCP 图像内容块(可让视觉型客户端真正看到图片);可选 max_width/max_height 经由 Plex 转码器的路径路由

plex_save_image

与 plex_get_image 相同输入面,但将字节写入 MCP_IMAGE_SAVE_DIR 下的磁盘(默认 /data/images/)并返回路径 + 大小。可将宿主机路径绑定到该目录,以桥接到下游管道(ImageMagick、filesystem-mcp 消费者等),而无需视觉渲染。

plex_download_logs

获取 Plex Media Server 自身的诊断日志包(ZIP),写入 MCP_LOG_SAVE_DIR(默认 /data/logs/)

plex_list_posters

列出某个项目的所有候选海报(由代理提供、本地扫描、之前上传的),包括当前生效的是哪一个

plex_set_poster

从 plex_list_posters 中按候选海报的 poster_rating_key 选择已存在的候选海报为当前生效海报

plex_upload_poster

从外部 URL(Plex 获取)或 MCP_IMAGE_SAVE_DIR 下的本地文件添加新海报。默认自动选择; select=false 表示添加但不更改当前显示内容

Related MCP server: Plex Assistant MCP

Configuración

Dos variables de entorno, ambas obligatorias:

Variable

Ejemplo

Notas

PLEX_URL

http://192.168.1.50:32400

URL base de tu servidor Plex

PLEX_TOKEN

(ver más abajo)

Token de autenticación de Plex

Para encontrar tu token de Plex, consulta la guía de Plex sobre cómo encontrar un token de autenticación.

Variables opcionales

Todas tienen valores predeterminados; configúralas solo para anularlos.

Variable

Predeterminado

Notas

MCP_FETCH_TIMEOUT_MS

30000

Tiempo de espera para cada solicitud saliente de Plex, excepto descargas de registros.

MCP_IMAGE_MAX_BYTES

4194304 (4 MiB)

Límite máximo para plex_get_image/plex_save_image.

MCP_LOG_MAX_BYTES

52428800 (50 MiB)

Límite máximo para plex_download_logs.

MCP_LOG_TIMEOUT_MS

120000 (2 min)

Tiempo de espera para plex_download_logs — separado de MCR_FETCH_TIMEOUT_MS porque el ZIP de registros tiene un perfil de tamaño/latencia diferente.

MCP_SESSION_IDLE_TIMEOUT_MS

3600000 (1 h)

Expulsa una sesión MCP en modo HTTP después de esta inactividad.

LOG_LEVEL, MCP_ALLOWED_HOSTS/MCP_ALLOWED_ORIGINS y HOST_IMAGE_DIR/HOST_LOG_DIR se tratan en sus propias secciones a continuación (Registro, endurecimiento del transporte HTTP, despliegue de Porttainer), ya que cada una necesita más de una nota de una línea.

¿Plex en el mismo host que el contenedor? Usa PLEX_URL=http://host.docker.internal:32400. El archivo de compose mapea host.docker.internal a la puerta de enlace del host de Docker vía extra_hosts, para que el contenedor pueda alcanzar un servidor Plex ejecutándose en el host. El propio nombre de host (p. ej. mi-nas) no se resolverá desde dentro del contenedor sin ese mapeo.

Modos de transporte

Modo

Cuándo usarlo

Cómo iniciarlo

stdio (predeterminado)

Invocación directa por Claude Desktop / clientes MCP

docker run -i --rm ... plex-mcp (sin MCP_PORT)

HTTP transmisible

Despliegue de larga duración (Portainer, Compose, k8s)

Configurar MCP_PORT=3000 (ya hecho en docker-compose.yml)

En modo HTTP, el servidor expone:

  • POST/GET/DELETE /mcp — punto de conexión HTTP transmisible de MCP (según especificación)

  • GET /health — sonda de vida (usada por Docker healthcheck)

El modo HTTP no tiene autenticación de llamador — TLS (más abajo) cifra el tráfico pero no identifica al llamador. Vincula solo a una red privada. Confía en el cortafuegos del host o aislamiento de LAN. Esto no no debe exponerse a internet pública sin agregar primero autenticación de token portador.

Habilitando HTTPS

HTTPS es opcional. Orden de resolución en el arranque:

  1. Trae tu propio certificado — configura tanto MCP_TLS_CERT_FILE como MCP_TLS_KEY_FILE con rutas de archivo PEM. Úsalo al terminar Let's Encrypt o una CA interna. El servidor los lee en el arranque; reinicia el contenedor para recoger archivos renovados.

  2. Certificado autogestionado (recomendado solo para LAN) — configura MCP_TLS=auto. El servidor genera un certificado ECDSA P-256 autofirmado en el primer arranque, lo escribe en MCP_TLS_DIR (predeterminado /datos/certs) y lo reutiliza en los siguientes arranques. Cuando el certificado está dentro de los 30 días de su caducidad se vuelve a generar automáticamente.

  3. De lo contrario, el servidor permanece en HTTP simple (predeterminado actual).

Variable

Predeterminado

Notas

MCP_TLS

sin establecer

auto / true / on / 1 para habilitar el modo autogestionado

MCP_TLS_DIR

/datos/certs

Dónde viven customers. Monta un volumen para persistir.

MCP_TLS_SAN

DNS:localhost,IP:127.0.0.1

Nombres alternativos de sujeto. Entradas separadas por comas DNS: / IP:.

MCP_TLS_CN

primer DNS SAN, si no plex-mcp

Nombre común del certificado.

MCP_TLS_DAYS

365

Período de validez. Se rota cuando quedan <30 días Kuando quedan <30 días.

MCP_TLS_CERT_FILE

sin definir

Certificado BYO (PEM). Reemplaza MCP_TLS=auto cuando se establece junto con la clave.

MCP_TLS_KEY_FILE

sin definir

Clave BYO (PEM).

En el arranque, el servidor registra la huella SHA-256 del cert y notAfter. Fija la huella en el lado del cliente; o confía en el certificado en el almacén de claves de tu sistema operativo para navegadores y herramientas CLI.

Cuando TLS está activado, el healthcheck de compose necesita la --no-checar-certificado. Actualizar la línea test: a ["CMD", "wget", "--no-check-certificate", "-q", "-O-", "https://localhost:3000/health"].

Apuntando mcp-remote a un endpoint HTTPS

Para un certificado autofirmado, puedes fijar el archivo de certificado de Node (bundle CA) o omitir la verificación en el cliente (solo LAN):

# Trust the server's self-signed cert (preferred):
NODE_EXTRA_CA_CERTS=./server.crt \
  npx -y mcp-remote https://nas.local:3443/mcp

# Or skip verification for quick testing (LAN-only):
NODE_TLS_REJECT_UNAUTHORIZED=0 \
  npx -y mcp-remote https://nas.local:3443/mcp

Alternativa de proxy inverso

TLS en el proceso es conveniente cuando no tienes un controlador de ingreso ya. Si tienes Caddy, Plex/Plex, o nginx delante de tu servicio/servicio en el hogar, el patrón más idiomático es terminar TLS en el proxy (con Let's Encrypt automático) y mantener plex-mcp en HTTP simple detrás. Ambos enfoques son intercambiables — elige El que coincida con tu configuración existente.

Autenticación OAuth 2.1 bearer-token (opt-in, aún no práctica)

Sopungojo.

The text "Soporte de código para autenticación..." etc.

Let's translate.

Need include "es eso" etc.

"### Autenticación OAuth 2.1 bearer-token (opt-in, aún no práctica)

El soporte de código para la autenticación de recursos protegidos OAuth 2.1 existe (Alineación de SDK de ChatGPT ...) (ver docs/CHATGPT-APPS-SDK.md para el plan completo), pero en realidad aún no es algo que puedas activar y usar: necesita un proveedor de identidad OAuth 2.1 real que emita tokens, y núminguno está aprovisionado para este despliegue (eso es Fase 3, en algún momento no iniciada). Documentado aquí para que conste, no como instrucciones.

Variable

Notas

MCP_OAUTH_ISSUER

URL del proveedor de identidad. Configurar esto activa la autenticación — no configurado (predeterminado) significa sin autenticación, idéntico al comportamiento actual.

MCP_OAUTH_AUDIENCE

Obligatorio una vez que MCP_OAUTH_ISSUER está configurado. La reclamación aud esperada — debe coincidir con la URL canónica pública de este servidor. El servidor se niega a iniciar si falta.

MCP_OAUTH_REQUIRED_SCOPES

Separadas por comas. Predeterminado: plex:read.

Cuando está activado, cada solicitud /mcp necesita Authorization: Bearer <jwt> — emitido por el IdP configurado, con dicha audiencia y IAM. /health no se ve afectado, since? health? translate: /health never? Need: "time a separate route, y el propio healthcheck de Docker no tiene forma de adjuntar un token bearer". Then "/.well-known/oauth-protected? Actually source says /.well-known/oauth-protected-resource. Need keep URL. We'll translate text.

Need also translate "El endpoint de MCP estará en http://<host>:${HOST_PORT}/mcp." Wait that's in Docker section. Need keep.

Ejecutar con Docker (stdio, bajo demanda)

docker build -t plex-mcp .
docker run -i --rm \
  -e PLEX_URL=http://192.168.1.50:32400 \
  -e PLEX_TOKEN=your-token \
  plex-mcp

La imagen ghcr.io/carldog/plex-mcp:latest (multi-arch: linux/amd64 + linux/arm64), publicado por CI en cada push a main.

# Required env vars (or use a .env file):
export PLEX_URL=http://192.168.1.50:32400
export PLEX_TOKEN=your-token
export MCP_ALLOWED_HOSTS=nas.local:3001  # required — see below
export HOST_PORT=3001  # optional, defaults to 3001

docker compose up

The MCP endpoint will be at...

Need "El endpoint de MCP estará en..." Actually in English "The MCP endpoint will be http://<host>:${HOST_PORT}/mcp." Need translate.

To rebuild from source:

docker build -t ghcr.io/carldog/plex-mcp:latest .
docker compose up

Desplegar vía Portainer (Stack from Git)

  1. En Portainer, Stacks → Add Stack → Repository.

  2. URL del repositorio: https://github.com/CarlDog/plex-mcp

  3. Ruta de compose: docker-compose.yml

  4. Variables de entorno: configurar PLEX_URL, PLEX_TOKEN, MCP_ALLOWED_HOSTS, HOST_IMAGE_DIR y HOST_LOG_DIR — todas obligatorias (ver más abajo); opcionalmente HOST_PORT.

  5. Desplegar. El healthcheck alcanza verde en ~10 segundos.

MCP_ALLOWED_HOSTS es obligatoria en modo HTTP

Una lista separada por comas de valores del encabezado Host que el servidor acepta en /mcp — p. ej. nas.local:3001 (debe coincidir con el host:puerto que un cliente realmente marca, incluido el HOST_PORT mapeado). El servidor se niega a arrancar en modo HTTP sin esto, y docker compose config falla del mismo modo si no está configurado: ambos fallan antes de que el contenedor aparezca, deliberadamente, en lugar de arrancar en un estado sin protección silenciosamente.

Esto existe porque enlazar 0.0.0.0 dentro de un contenedor no es una frontera de acceso real, como sí lo es una vinculación de loopback en un host desnudo: una página cargada en cualquier navegador en la LAN puede hacer DNS rebinding — apuntando su propio nombre de host a la IP de este contenedor— y conducir herramientas (incluyendo escrituras como plex_delete_playlist) como un diputado confundido, anulando por completo "solo LAN, sin token bearer" como postura de seguridad. La lista de permitidos del host cierra esa brecha sin requerir autenticación completa. MCP_ALLOWED_ORIGINS la que está predeterminada para hacer lo mismo para el encabezado Origin — déjalo sin configurar a menos que un cliente basado en el navegador legítimamente necesite llamar directamente a este servidor; los clientes que no son del navegador (el puente mcp-remote, un fetch directo) nunca envían un encabezado Origin, así que el predeterminado vacío solo rechaza la forma exacta que realmente envía un ataque de rebinding DNS.

HOST_IMAGE_DIR y HOST_LOG_DIR son obligatorios — no hay predeterminado relativo

Ambas rutas de host en el archivo compose son ${VAR:?}: no hay predeterminado de respaldo, por lo que docker compose up / un redeploy de Portainer falla rápidamente con un error claro si falta alguna, en lugar de arrancar en un estado roto.

Esto solía ser un predeterminado blando ${VAR:-./datos/imagenes}, que solo es seguro para un docker compose up local desde la clonación estable. En un stack de Portainer es una trampa: cada redeploy clona el repositorio en un directorio nuevo por commit (/datos/compose/<stack-id>/<commit>/), donde una ruta relativa como ./datos/imagenes no existe. Docker rechazó y el contenedor se quedó atascado en created — nunca arranersey. Eso también golpeó los redeploys automáticos (git poll/actualización de imagen), así que un stack previamente saludable se cayó sin acción manual; el único síntoma que no singular fue el contenedor created. Esto sacó al stack desplegado ~10 horas el 2026-07-31 — docker-deployments.md v10 y 2026-07-31-compose-relative-defaults-break-portainer-git. El archivo compose ahora hace que el requisito sea estructural en lugar de una convención de documentación.

Configúralos como rutas absolutas del host en las variables de entorno del stack:

It then says "Set both..." etc.

Need all.

Now we need to perhaps be less formal? No, need preserve.

Also there is "both" -> "ambas".

Let's finalize.

Need possibly include all sections from original.

Will output full translation; must keep all headings/code.

Let's ensure translation spans.

I will now translate entire rest.

Need perhaps "### Ejecutar con Docker (stdio, bajo demanda)" -> "## Run with Docker (stdio, on demand)" maybe "Ejecabc" ok.

Need "GXP2" as "GXP2" and "GXP3" maybe not.

Wait: In source, there is "GXP1" near beginning and "GXP3" etc. Actually there are tokens `GXP2? The prompt says placeholder tokens (GXP1, GXP2) but I see "GXP2", "GXP3", "GXP4"? Let's check: " GXP2", "GXP3"? In user text line: "GXP2", "GXP3", "GXP4"? Actually at beginning after "La imagen" there are "GXP2" etc. Anyway keep them as they are.

Also keep "PLEX_URL"? No, PLEX_URL.

Also HOST_IMAGE_DIR, HOST_LOG_DIR, etc. exact.

Also --no-cert-check maybe keep.

In "The compose file maps..." etc need translate but keep host.docker.internal.

Need all.

Let's finalize.

Potential subtle phrase: "logic of the documentation" no.

Need preserve "e.g." etc.

Ok.

Let's now produce final translation.

Need maintain Spanish as spoken in Spain, using "tú" form. "configuration" -> "configuración". "server" -> "servidor". "request" -> "solicitud". "bearer token" -> "token de portador" or "token Bearer"? Translate "Authorization: Bearer" as Authorization: Bearer. Actually code? In prose with backticks? To keep? Since it's in code span, keep. "IdP" in code, keep. "aud" claim? "aud" in backticks, keep. "scopes" -> "ámbitos". "endpoint" -> "endpoint" (often kept).

Need ensure "host" "permisos" etc.

Let's translate.

Important: Does "MCP" is in code span and on its own. It's "MCP" product name? It appears in code; keep.

"Configuración" etc.

Need ensure "deployment" -> "implementación". "Portainer" product? keep. But maybe in Spanish there is "Panel"? No, keep "Portainer".

"Registro" -> "Registro".

Let's translate.

I will output final with all parts. Since final cannot use code fence? The instruction says "Return only translated text, no wrapper nor code fence." Usually means don't wrap with markdown code fences, but can output markdown. So fine.

Need maybe not include comments? All good.

Let's strive for exact.

Need maybe "Mar" no.

Need ensure "44 MiB" vs "44 MiB"? Spain uses "MiB" maybe. Keep.

Actually, in the table cell: 4194304 (4 MiB) maybe in code? Kept. Need maybe "sin configurar" etc.

Ok.

Now need translate text from "Enabling HTTPS" section:

Need "By putting proxy..." etc.

In "reverse proxy" text "Términos" etc. Let's translate.

Potential issue: "byo" abbreviation? "BYO" maybe "Trae tu propio". In line. We'll handle.

Now, let## Configuración

Dos variables de entorno, ambas obligatorias:

Variable

Ejemplo

Notas

PLEX_URL

http://192.168.1.50:32400

URL base de tu servidor Plex

PLEX_TOKEN

(ver más abajo)

Token de autenticación de Plex

Para encontrar tu token de Plex, consulta la guía de Plex sobre [cómo encontrar un token de autenticación](https://support.plex пуним.plex/articles/204059436-finding-an-authentication-token-x-plex-token/).

Variables opcionales

Todas tienen valores predeterminados; configúralas solo para sobrescribir.

Variable

Predeterminado

Notas

MCP_FETCH_TIMEOUT_MS

30000

Tiempo de espera para cada solicitud saliente de Plex, excepto descargas de registros.

MCP_IMAGE_MAX_BYTES

4194304 (4 MiB)

Límite de tamaño para plex_get_image/plex_save_image.

MCP_LOG_MAX_BYTES

52428800 (50 MiB)

Límite de tamaño para plex_download_logs.

MCP_LOG_FETCH_TIMEOUT_MS

120000 (2 min)

Tiempo de espera para plex_download_logs — separado de MCP_FETCH_TIMEOUT_MS porque el ZIP de registros tiene un perfil de tamaño/latencia diferente.

MCP_SESSION_IDLE_TIMEOUT_MS

3600000 (1 h)

Expulsa una sesión MCP en modo HTTP después de esta inactividad.

LOG_LEVEL, MCP_ALLOWED_HOSTS / MCP_ALLOWED_ORIGINS y HOST_IMAGE_DIR / HOST_LOG_DIR se tratan en sus propias secciones a continuación (Registro, endurecimiento del transporte HTTP, implementación de Portainer), ya que cada una necesita más de una nota de una línea.

¿Plex en el mismo host que el contenedor? Usa PLEX_URL=http://host.docker.internal:32400. El archivo de compose mapea host.docker.internal a la puerta de enlace del host de Docker vía extra_hosts, para que el contenedor pueda alcanzar un servidor Plex ejecutándose en中国文化oraly en el host. El propio hostname del host (p. ej. mi-nas) no se resolverá desde dentro del contened计数 sin ese mapeo.

Modos de transporte

Modo

Cuándo usarlo

Cómo iniciarlo

stdio (predeterminado)

Invocación directa por Claude Desktop / clientes MCP

docker run -it --rm ... plex-mcp (sin MCP_PORT)

HTTP transmisible

Implementación de larga duración (Portainer, Compose, k8s)

Configurar MCP_PORT=3000 (ya hecho en docker-compose.yml)

En modo HTTP, el servidor expone:

  • POST/GET/DELETE /mcp — punto de conexión HTTP transmisible de MCP (según especificación)

  • GET /health — sonda de vida (utilizada por el healthcheck de Docker)

El modo HTTP no autentica al llamante — TLS (abajo) cifra el tráfico pero no identifica al llamante. Vincula solo a una red privada. Confía en el cortafzatuegos del host o el aislamiento de la LAN. No lo expongas a la internet pública sin añadir antes autenticación de token bearer.

Habilitación de HTTPS

HTTPS es optativo. Orden de resolución en el arranque:

  1. Trae tu propio certificado — configura tanto MCP_TLS_CERT_FILE como MCP_TLS_KEY_FILE a rutas PEM. Úsalo al terminar Let's Encrypt o una CA interna. El servidor los lee al arrancar; reinicia el contenedor para recoger archivos renovados.

  2. Certificado autogestionado (recomendado solo para LAN) — configura MCP_TLS=auto. El servidor genera un certificado autofirmado ECDSA P-256 en el primer arranquefab, lo escribe en MCP_TLS_DIR (predeterminado /datos/certs) y lo reutiliza en arranques siguientes. Cuando el certificado está a menos de 30 días de su caducidad, se regenera automáticamente.

  3. De lo contrario, el servidor permanece en HTTP simple (predeterminado de hoy).

Variable

Predeterminado

Notas

MCP_TLS

sin definir

auto / true / on / 1 para activar el modo autogestionado

MCP_TLS_DIR

/datos/certs

Dónde viven server.crt / server.key. Monta un volumen para persistir Funeral.

MCP_TLS_SAN

DNS:localhost,IP:127.0.0.1

Nombres alternativos del sujeto. Entradas separadas por comas DNS: / IP:.

MCP_TLS_CN

primer DNS SAN, si no plex-mcp

Nombre común del certificado.

MCP_TLS_DAYS

365

时期 de validez. El certificado rota cuando quedan <30 días.

MCP_TLS_CERT_FILE

sin definir

Certificado BYO (PEM). Rempleza MCP_TLS=auto cuando se configura junto con la clave.

MCP_TLS_KEY_FILE

sin definir

Clave BYO (PEM).

En el arranque, el servidor registra la huella SHA-256 del certificado y la notAfter. Anuncia la huella en el lado del cliente; o confía en el certificado en el almacén de claves de tu SO para navegadores y herramientas CLI.

Cuando TLS está activado, el healthcheck de compose necesita el indicador --no-check-certificate. Actualiza la línea test: a ["CMD", "w Просечан", "wget", "--no-check-certificate", "-q", "-O-", "https://localhost:3000/health"].

Apuntando mcp-remote a un endpoint HTTPS

Para un certificado autofirmado, puedes hacer pin del archivo de certificados de Node (en bundle de CA) u omitir la verificación en el cliente (según el check-certificate) (solo LAN):

# Trust the server's self-signed cert (preferred):
NODE_EXTRA_CA_CERTS=./server.crt \
  npx -y mcp-remote https://nas.local:3443/mcp

# Or skip verification for quick testing (LAN-only):
NODE_TLS_REJECT_UNAUTHORIZED=0 \
  npx -y mcp-remote https://nas.local:3443/mcp

Alternativa de proxy inverso

TLS en proceso es conveniente cuando no tienes un controlador de ingreso ya. Si tienes Caddy, Traefik, o nginx cristal delante de tus servicios domést newcomيارى, transparente, están las más idiomáticas donde terminar TLS en el proxy原 (con Let's Encrypt automático) y mantener plex-mcp en HTTP simple detrás. Ambos enfoques son intercambiables: elige el que coincزا con tu configuración existente.

Autenticación OAuth 2.1 de token bearer (opt-in, aún no práctica)

El soporte de código para la autenticación de recursos protegidos OAuth 2.1 trasera web (SDK de la app de ChatGPT - véase docs/CHATGPT-APPS-SDK.md para el plan completo), pero aún no es que puedas encender y usar: necesita un proveedor de identidad OAuth 2.1 real que emita tokens, y no está previsionado para este despliegue (eso es la Fase 3, sin empezar). Documentado aquí para completitud, no como cómo hacerlo.

Variable

Notas

MCP_OAUTH_ISSUER

URL del proveedor de identidad. Configurar esto opta por autenticación.

MCP_OAUTH_AUDIENCE

Necesario cuando se configura MCP_OAUTH_ISSUER. La reclamación aud esperada — debe coincidir con la URL canónica pública de este servidor.

MCP_OAUTH_REQUIRED_SCOPES

Separadas por comas. Predeterminado: plex:read.

Cuando está activado, cada solicitud /mcp necesita Authorization: Bearer <jwt> — emitido por el IdP configurado, con el privilegio de audiencia y la audiencia correcta y el ámbito correcto. /health nunca se ve golden (una ruta separada es una forma de Docker no tiene manera de adjuntar un token bearer). /.well-known/oauth-protected-resource se sirve de acuerdo con la RFC 9728.

Ejecutar con Docker (stdio, bajo demanda)

docker build -t plex-mcp .
docker run -i --rm \
  -e PLEX_URL=http://192.168.1.50:32400 \
  -e PLEX_TOKEN=your-token \
  plex-mcp

La imagen de ghcr.io/carldog/plex-mcp:latest (multi-arquitectura: linux/amd64 + linux/arm64), publicada por CI en cada push para main.

# Required env vars (or use a .env file):
export PLEX_URL=http://192.168.1.50:32400
export PLEX_TOKEN=your-token
export MCP_ALLOWED_HOSTS=nas.local:3001  # required — see below
export HOST_PORT=3001  # optional, defaults to 3001

docker compose up

El estilo de vida de MySQL.

No hay problema.

Implementar vía Portainer (Stack desde Git)

  1. En Portainer, Stacks → Add Stack → Repository.

  2. Repository URL: https://github.com/CarlDog/plex-mcp

  3. Compose path: docker-compose.yml

  4. Environment variables: definir PLEX_URL, PLEX_TOKEN, MCP_ALLOWED_HOSTS, HOST_IMAGE_DIR y HOST_LOG_DIR — todos obligatorios (ver más abajo); opcionalmente HOST_PORT.

  5. Deploy. El healthcheck llega a verde en ~10 segundos.

MCP_ALLOWED_HOSTS es obligatorio en modo HTTP

Lista separada por comas de valores del encabezado Host que el servidor acepta en /mcp — por ejemplo, nas.local:3000 (debe coincidir con el host:puוניב de una cliente, incluyendo el HOST_PORT mapeado). El servidor se niega a iniciar en modo审查 # ... # ... En el modo HTTP sin él, y docker compose config falla de la misma manera si no está configurado: ambos fallan antes de que el contenedor aparezca por fin, deliberadamente, en lugar de arrancar a un estado sin protección en silencio.

Esto existe porque vincular 0.0.0.0 dentro de un contenedor no es una frontera de acceso real, la forma en que un lazo de bucle en un host huir: sin ... no es real seguro. Una página cargada en un navegador en cualquier lugar de la LAN puede hacer un DNS rebinding, lo que ... apuntando su propio nombre de host a la IP de este contenedor, y pilotar herramientas, incluyendo escrituras como plex_delete_playlist, como un diputado confundido, completamente "solo-LAN, sin token de portador" como postura de seguridad. La lista de permitidos del host cierra esa brecha sin requerir autenticación completa. MCP_ALLOWED_ORIGINS (opcional, predeterminado vacío) hace lo mismo para el encabezado Origin —déjalo sin configurar a menos que una cliente basada en navegador legítimamente necesite llamar a este servidor directamente; los clientes no navegadores (el jugador mcp-remote, un fetch directo) nunca envían un encabezado Origin, así que el predeterminado vacío solo rechaza la forma que un ataque de rebinding DNS realmente envía.

HOST_IMAGE_DIR y HOST_LOG_DIR son obligatorio — sin predeterminado relativo

Ambas rutas de host y localmente ...

check.

Let's translate the final parts.

Ambas rutas de hosts en el archivo compose son ${VAR:?}: no hay default, así que docker compose up / un redeploy de Portainer falla rápidamente con un error claro si cualquiera de los dos (se queda sin asignar, en lugar de arrancar con un estado roto.

Esto antes era un default blando ${VAR:-./data/images}, que solo es seguro para una subida docker compose local desde un clon estable. En un stack de Portainer es una trampa: cada redeploy clona el repositorio en un directorio nuevo por commit (/data/compose/<stack-id>/<commit>/), donde una ruta relativa como ./datos/imágenes no existe. Docker rechazó el bind mount y el contenedor se quedó atascado en created — nunca arrancó. Eso también golpeó los redeploys automáticos (image update, git poll), así que un stack que estaba sano se cayó sin ninguna acción manual; el único síntoma era el contenedor atascado en created. Eso tumbó el stack desplegado durante ~10 horas el 2026-07-31 — docker-deployments.md rule #10 y 2026-07-31-compose-relative-defaults-break-portainer-git. El archivo compose ahora hace que el requisito sea estructural, en lugar de una convención solo de documentación.

Establece las rutas absolutas del host en las variables de entorno del stack.

  • HOST_IMAGE_DIR — el directorio de salida de plex_save_image. Recomendado: el directorio del host que respalda el montaje /media/_mcp-scratch de filesystem-mcp — p. ej. /volume1/Media/_mcp-scratch en un Synology NAS — que mantiene el pipeline plex_search → plex_save_image → filesystem-mcp en un único directorio compartido.

  • HOST_LOG_DIR — el directorio de salida de plex_download_logs, separado de HOST_IMAGE_DIR porque un ZIP de diagnóstico no es un artefacto multimedia — p. ej. /volume1/docker/plex-mcp/logs en un Synology NAS (acorde con la convención de datos de aplicación por contenedor de esta flota).

Asegúrate de que ambos directorios existan en el host antes del primer despliegue: Docker no crea automáticamente un origen de bind-mount que falte; simplemente se niega a iniciar el contenedor.

Uso con Claude Desktop

stdio (invocación local)

{
  "mcpServers": {
    "plex": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "PLEX_URL", "-e", "PLEX_TOKEN",
        "plex-mcp"
      ],
      "env": {
        "PLEX_URL": "http://192.168.1.50:32400",
        "PLEX_TOKEN": "your-token"
      }
    }
  }
}

HTTP (servidor MCP remoto)

{
  "mcpServers": {
    "plex": {
      "url": "http://nas.local:3001/mcp"
    }
  }
}

(Requiere Claude Desktop o un cliente que admita MCP HTTP remoto.)

Desarrollo local

npm install
cp .env.example .env  # then edit
PLEX_URL=... PLEX_TOKEN=... npm run dev               # stdio
MCP_PORT=3000 MCP_ALLOWED_HOSTS=localhost:3000 PLEX_URL=... PLEX_TOKEN=... npm run dev # HTTP

Registro

El servidor emite registros estructurados a stderr (stdout es el protocolo de transmisión de MCP en modo stdio y no debe contaminarse). Formato:

2026-04-29T15:30:00.000Z INFO [tool:plex_browse] invoke section_id=7 type=show limit=2
2026-04-29T15:30:00.337Z INFO [tool:plex_browse] ok ms=337

Configura la verbosidad mediante la variable de entorno LOG_LEVEL (por defecto info):

Nivel

Muestra

error

Solo errores

warn

+ Respuestas Plex 4xx

info (por defecto)

+ Invocaciones y finalizaciones de herramientas

debug

+ Cada llamada a la API de Plex con método, ruta, estado, ms

trace

(reservado)

Los registros del contenedor son capturados por el controlador json-file de Docker y se rotan automáticamente (10MB × 3 archivos = límite de ~30MB; el más antiguo se elimina al rotar). Consúltalos con docker logs plex-mcp o docker logs -f.

Seguridad

  • El contenedor se ejecuta como un usuario no root (plexmcp).

  • El token de Plex se pasa mediante una variable de entorno — nunca lo incrustes en la imagen.

  • Un .githooks/pre-commit ejecuta gitleaks en cada commit. Actívalo una vez por clon: git config core.hooksPath .githooks

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables users to manage and control their Plex media library through natural language commands in MCP-compatible AI clients. It supports searching content, managing playlists, tracking library statistics, and monitoring live viewing sessions.
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP server for reelgrep - browse and search your local video library from any MCP client.
    10 npm
    MIT