.codex-plugin — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited .codex-plugin (MCP Server) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<div align="center"> <h1>@cyanheads/seerr-mcp-server</h1> <p><b>Search Jellyseerr/Overseerr, check availability, and create guarded media requests via MCP. STDIO or Streamable HTTP.</b> <div>6 Tools • 1 Resource</div> </p> </div>
<div align="center">
</div>
<div align="center">
</div>
A workflow MCP server over a self-hosted Jellyseerr / Overseerr instance — the request layer that fronts Jellyfin/Plex/Emby plus Radarr and Sonarr. The unit of work is not "download a movie"; it is search → resolve the exact TMDB-backed title → check availability and request state → create a guarded request that Radarr/Sonarr act on. Jellyseerr owns permissions, quotas, routing, and status; this server never touches Radarr/Sonarr directly.
Two properties make it safe to hand an agent:
seerr_request_media) defaults to mode: preview — it resolves the title and returns the exact payload that _would_ be submitted without writing anything. The real request fires only on mode: request, and asks for an explicit confirmation first when the client supports elicitation.{ id, displayName }, and root-folder paths are gated behind an explicit includePaths flag.Six tools covering the request workflow — discover (search) → confirm (get) → understand routing (service_options) → request (request_media) → track (request_status / list_requests):
| Tool | Description |
|---|---|
seerr_search_media | Search movies and TV by title; returns ranked matches with TMDB ID, year, overview, and decoded availability when Jellyseerr already tracks the title. The required first step before requesting. |
seerr_get_media | Fetch exact movie/show details by TMDB ID + media type to confirm the title before a write; for TV, a per-season summary or one season's episode list. |
seerr_list_requests | List recent requests with status/type/requester filters; echoes the applied filters and decodes every numeric status. |
seerr_request_media | Guarded write. Previews the request payload by default (mode: preview); creates the request only on mode: request with an elicited confirmation. |
seerr_request_status | Fetch one request by ID — decoded request + media availability (incl. 4K), requester, routing summary, and a state-tuned next-step hint. |
seerr_service_options | Summarize configured Radarr/Sonarr services, default quality profiles, and instance capability flags (4K, partial requests, specials, media server). Filesystem paths redacted unless includePaths. |
Every status field is decoded to { raw, label } — both the numeric code Jellyseerr returns and a human label — so an agent never has to hardcode the enum mapping.
seerr_search_mediaTitle disambiguation entry point. Wraps GET /search, filters to movies and TV (people are always excluded), and decodes availability when the title is tracked.
movie / tv / all media-type filterstatus, plus status4k when 4K is enabled) for tracked titles onlylimit to cap output sizelanguage for localized titles/overviews[] with a guidance notice, not an errorseerr_get_mediaConfirm the exact title before a write. Wraps GET /movie/{id} or GET /tv/{id}, optionally a season's episodes.
seasonNumber for a per-season summary, or pass one to fetch that season's episode list (season 0 is Specials)media_not_found with a search-recovery hint (Jellyseerr's raw HTTP 500 is classified in the service layer)seerr_list_requestsReview recent requests and their lifecycle. Wraps GET /request.
filter (pending, processing, available, failed, …), mediaType, and requestedById filtersadded) or last-changed (modified), ascending or descendingtake / skip pagination; the enrichment trailer echoes the filter set the server applied{ id, displayName }; titles aren't on request objects, so they're omitted here — fetch one with seerr_get_media when neededseerr_request_mediaThe only mutation in the surface, and it is triple-guarded:
POST /request payload that _would_ be submitted — no write. A sloppy call shows the payload and changes nothing.ctx.elicit confirmation when the client supports it; declining cancels before submission.seasons: "all" or an explicit list (e.g. [1, 2]); Specials are excluded unless the instance enables themserverId, profileId, rootFolder, languageProfileId) — omit to use Jellyseerr's defaults (recommended)duplicate_request pointing back at itseerr_service_optionsLets an agent reason about request capability and routing without a separate status tool. Fans out service + settings + version reads with Promise.allSettled, so one failed leg degrades to a disclosed notice rather than failing the call.
movie4kEnabled / series4kEnabled / partialRequestsEnabled / specialEpisodesEnabled flagsincludePaths: true| Type | Name | Description |
|---|---|---|
| Resource | seerr://request/{requestId} | Read-once summary of one request — decoded status + media availability + routing. Mirrors seerr_request_status. |
All request data is also reachable via tools — request _enumeration_ is the job of seerr_list_requests (filterable, the tool-only access path), so the collection is intentionally not exposed as a resource. There are no prompts; the guarded-write workflow lives in the tool, not a prompt template.
Built on @cyanheads/mcp-ts-core:
none, jwt, oauthin-memory, filesystem, Supabase, Cloudflare KV/R2/D1Jellyseerr-specific:
{ raw, label } everywhere, forward-compatible with new Jellyseerr status codesAgent-friendly output:
{ id, displayName } and drops operator email, Plex/Jellyfin tokens, internal serviceUrl, and filesystem paths before any output reaches the modelmedia_not_found, request_not_found, seasons_required, four_k_not_enabled, duplicate_request, and more carry a recovery hint so callers can branch and retry without parsing proseThis server connects to your own Jellyseerr/Overseerr instance — there is no public hosted endpoint. Add the following to your MCP client configuration file, pointing SEERR_BASE_URL at your instance and supplying its API key.
{
"mcpServers": {
"seerr-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/seerr-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"SEERR_BASE_URL": "http://localhost:5055",
"SEERR_API_KEY": "your-api-key"
}
}
}
}Or with npx (no Bun required):
{
"mcpServers": {
"seerr-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/seerr-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"SEERR_BASE_URL": "http://localhost:5055",
"SEERR_API_KEY": "your-api-key"
}
}
}
}For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 SEERR_BASE_URL=http://localhost:5055 SEERR_API_KEY=your-api-key bun run start:http
# Server listens at http://localhost:3010/mcpgit clone https://github.com/cyanheads/seerr-mcp-server.gitcd seerr-mcp-serverbun installcp .env.example .env
# edit .env — set SEERR_BASE_URL and SEERR_API_KEYAll configuration is validated at startup via Zod schemas in src/config/server-config.ts. Key environment variables:
| Variable | Description | Default |
|---|---|---|
SEERR_BASE_URL | Required. Base URL of the Jellyseerr/Overseerr instance, e.g. http://localhost:5055. The service appends /api/v1 — no /api/v1 suffix, no trailing slash. | — |
SEERR_API_KEY | Required. Jellyseerr API key (Settings → General → API Key). Sent as the X-Api-Key header. | — |
SEERR_REQUEST_TIMEOUT_MS | Per-request HTTP timeout in milliseconds. | 15000 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for the HTTP server. | 3010 |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry instrumentation. | false |
See .env.example for the full list of optional overrides.
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http bun run devcheck # Lint, format, typecheck, security, changelog sync
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against specdocker build -t seerr-mcp-server .
docker run --rm \
-e SEERR_BASE_URL=http://host.docker.internal:5055 \
-e SEERR_API_KEY=your-api-key \
-p 3010:3010 \
seerr-mcp-serverThe Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/seerr-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers the six tools + one resource and inits the Seerr service. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). |
src/mcp-server/resources | Resource definitions (*.resource.ts). |
src/services/seerr | Jellyseerr API client, status decoders, and the PII/infra redaction normalizers. |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagesrc/mcp-server/*/definitions/index.tsIssues and pull requests are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testApache-2.0 — see LICENSE for details.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.