@cyanheads/seerr-mcp-server 0.1.0 → 0.1.2

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 (44) hide show
  1. package/AGENTS.md +19 -16
  2. package/CLAUDE.md +19 -16
  3. package/Dockerfile +11 -7
  4. package/LICENSE +1 -1
  5. package/README.md +7 -7
  6. package/changelog/0.1.x/0.1.1.md +30 -0
  7. package/changelog/0.1.x/0.1.2.md +22 -0
  8. package/changelog/template.md +5 -3
  9. package/dist/index.js +0 -0
  10. package/dist/mcp-server/resources/definitions/index.d.ts +1 -0
  11. package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -1
  12. package/dist/mcp-server/resources/definitions/request.resource.d.ts +7 -3
  13. package/dist/mcp-server/resources/definitions/request.resource.d.ts.map +1 -1
  14. package/dist/mcp-server/resources/definitions/request.resource.js +17 -5
  15. package/dist/mcp-server/resources/definitions/request.resource.js.map +1 -1
  16. package/dist/mcp-server/tools/definitions/index.d.ts +152 -142
  17. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  18. package/dist/mcp-server/tools/definitions/list-requests.tool.d.ts +16 -9
  19. package/dist/mcp-server/tools/definitions/list-requests.tool.d.ts.map +1 -1
  20. package/dist/mcp-server/tools/definitions/list-requests.tool.js +74 -11
  21. package/dist/mcp-server/tools/definitions/list-requests.tool.js.map +1 -1
  22. package/dist/mcp-server/tools/definitions/request-media.tool.d.ts +7 -2
  23. package/dist/mcp-server/tools/definitions/request-media.tool.d.ts.map +1 -1
  24. package/dist/mcp-server/tools/definitions/request-media.tool.js +18 -4
  25. package/dist/mcp-server/tools/definitions/request-media.tool.js.map +1 -1
  26. package/dist/mcp-server/tools/definitions/request-status.tool.d.ts +4 -2
  27. package/dist/mcp-server/tools/definitions/request-status.tool.d.ts.map +1 -1
  28. package/dist/mcp-server/tools/definitions/request-status.tool.js +11 -4
  29. package/dist/mcp-server/tools/definitions/request-status.tool.js.map +1 -1
  30. package/dist/mcp-server/tools/definitions/search-media.tool.d.ts +1 -1
  31. package/dist/services/seerr/normalizers.d.ts +5 -1
  32. package/dist/services/seerr/normalizers.d.ts.map +1 -1
  33. package/dist/services/seerr/normalizers.js +21 -7
  34. package/dist/services/seerr/normalizers.js.map +1 -1
  35. package/dist/services/seerr/seerr-service.d.ts +12 -2
  36. package/dist/services/seerr/seerr-service.d.ts.map +1 -1
  37. package/dist/services/seerr/seerr-service.js +42 -16
  38. package/dist/services/seerr/seerr-service.js.map +1 -1
  39. package/dist/services/seerr/titles.d.ts +47 -0
  40. package/dist/services/seerr/titles.d.ts.map +1 -0
  41. package/dist/services/seerr/titles.js +96 -0
  42. package/dist/services/seerr/titles.js.map +1 -0
  43. package/package.json +11 -9
  44. package/server.json +3 -3
package/AGENTS.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** seerr-mcp-server
4
- **Version:** 0.1.0
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.10.6`
4
+ **Version:** 0.1.2
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.11.0`
6
6
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
8
8
  **Zod:** ^4.4.3
@@ -245,6 +245,7 @@ src/
245
245
  seerr-service.ts # Jellyseerr REST client (init/accessor pattern)
246
246
  status.ts # Pure status decoders → { raw, label }
247
247
  normalizers.ts # PII/infra redaction choke point (the security boundary)
248
+ titles.ts # Request → title join (deduped, capped, best-effort)
248
249
  errors.ts # Upstream-error classifier (500 → media_not_found, 404 → request_not_found)
249
250
  types.ts # Raw upstream + domain types
250
251
  mcp-server/
@@ -288,7 +289,6 @@ Available skills:
288
289
  | `techniques` | Catalog of reusable response/data-shaping patterns — overflow handling, payload shaping, retrieval |
289
290
  | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
290
291
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
291
- | `devcheck` | Lint, format, typecheck, audit |
292
292
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
293
293
  | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
294
294
  | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
@@ -321,20 +321,23 @@ When you complete a skill's checklist, check the boxes and add a completion time
321
321
 
322
322
  | Command | Purpose |
323
323
  |:--------|:--------|
324
- | `npm run build` | Compile TypeScript |
325
- | `npm run rebuild` | Clean + build |
326
- | `npm run clean` | Remove build artifacts |
327
- | `npm run devcheck` | Lint + format + typecheck + security + changelog sync |
324
+ | `bun run build` | Compile TypeScript |
325
+ | `bun run rebuild` | Clean + build |
326
+ | `bun run clean` | Remove build artifacts |
327
+ | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
328
328
  | `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so the advisory may be a stale-lockfile false positive. If it survives the refresh, it's real. |
329
- | `npm run tree` | Generate directory structure doc |
330
- | `npm run format` | Auto-fix formatting (safe fixes only) |
331
- | `npm run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
332
- | `npm test` | Run tests |
333
- | `npm run start:stdio` | Production mode (stdio) |
334
- | `npm run start:http` | Production mode (HTTP) |
335
- | `npm run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
336
- | `npm run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
337
- | `npm run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
329
+ | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
330
+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity (run by devcheck) |
331
+ | `bun run list-skills` | Print the skill registry |
332
+ | `bun run tree` | Generate directory structure doc |
333
+ | `bun run format` | Auto-fix formatting (safe fixes only) |
334
+ | `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
335
+ | `bun run test` | Run tests (Vitest — use `bun run test`, not `bun test`) |
336
+ | `bun run start:stdio` | Production mode (stdio) |
337
+ | `bun run start:http` | Production mode (HTTP) |
338
+ | `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
339
+ | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
340
+ | `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
338
341
 
339
342
  ---
340
343
 
package/CLAUDE.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** seerr-mcp-server
4
- **Version:** 0.1.0
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.10.6`
4
+ **Version:** 0.1.2
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.11.0`
6
6
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
8
8
  **Zod:** ^4.4.3
@@ -245,6 +245,7 @@ src/
245
245
  seerr-service.ts # Jellyseerr REST client (init/accessor pattern)
246
246
  status.ts # Pure status decoders → { raw, label }
247
247
  normalizers.ts # PII/infra redaction choke point (the security boundary)
248
+ titles.ts # Request → title join (deduped, capped, best-effort)
248
249
  errors.ts # Upstream-error classifier (500 → media_not_found, 404 → request_not_found)
249
250
  types.ts # Raw upstream + domain types
250
251
  mcp-server/
@@ -288,7 +289,6 @@ Available skills:
288
289
  | `techniques` | Catalog of reusable response/data-shaping patterns — overflow handling, payload shaping, retrieval |
289
290
  | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
290
291
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
291
- | `devcheck` | Lint, format, typecheck, audit |
292
292
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
293
293
  | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
294
294
  | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
@@ -321,20 +321,23 @@ When you complete a skill's checklist, check the boxes and add a completion time
321
321
 
322
322
  | Command | Purpose |
323
323
  |:--------|:--------|
324
- | `npm run build` | Compile TypeScript |
325
- | `npm run rebuild` | Clean + build |
326
- | `npm run clean` | Remove build artifacts |
327
- | `npm run devcheck` | Lint + format + typecheck + security + changelog sync |
324
+ | `bun run build` | Compile TypeScript |
325
+ | `bun run rebuild` | Clean + build |
326
+ | `bun run clean` | Remove build artifacts |
327
+ | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
328
328
  | `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so the advisory may be a stale-lockfile false positive. If it survives the refresh, it's real. |
329
- | `npm run tree` | Generate directory structure doc |
330
- | `npm run format` | Auto-fix formatting (safe fixes only) |
331
- | `npm run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
332
- | `npm test` | Run tests |
333
- | `npm run start:stdio` | Production mode (stdio) |
334
- | `npm run start:http` | Production mode (HTTP) |
335
- | `npm run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
336
- | `npm run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
337
- | `npm run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
329
+ | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
330
+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity (run by devcheck) |
331
+ | `bun run list-skills` | Print the skill registry |
332
+ | `bun run tree` | Generate directory structure doc |
333
+ | `bun run format` | Auto-fix formatting (safe fixes only) |
334
+ | `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
335
+ | `bun run test` | Run tests (Vitest — use `bun run test`, not `bun test`) |
336
+ | `bun run start:stdio` | Production mode (stdio) |
337
+ | `bun run start:http` | Production mode (HTTP) |
338
+ | `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
339
+ | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
340
+ | `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
338
341
 
339
342
  ---
340
343
 
package/Dockerfile CHANGED
@@ -4,15 +4,17 @@
4
4
  # This stage installs all dependencies (including dev), builds the TypeScript
5
5
  # source code into JavaScript, and prepares the production assets.
6
6
  # ==============================================================================
7
- FROM oven/bun:1.3 AS build
7
+ FROM oven/bun:1.3.14 AS build
8
8
 
9
9
  WORKDIR /usr/src/app
10
10
 
11
11
  # Copy dependency manifests for optimized layer caching
12
12
  COPY package.json bun.lock ./
13
13
 
14
- # Install all dependencies (including dev dependencies for building)
15
- RUN bun install --frozen-lockfile
14
+ # Install all dependencies (including dev dependencies for building).
15
+ # The BuildKit cache mount persists Bun's global package cache across builds.
16
+ RUN --mount=type=cache,target=/root/.bun/install/cache \
17
+ bun install --frozen-lockfile --ignore-scripts
16
18
 
17
19
  # Copy the rest of the source code
18
20
  COPY . .
@@ -28,7 +30,7 @@ RUN bun run build
28
30
  # application. It uses a slim base image and only includes production
29
31
  # dependencies and build artifacts.
30
32
  # ==============================================================================
31
- FROM oven/bun:1.3-slim AS production
33
+ FROM oven/bun:1.3.14-slim AS production
32
34
 
33
35
  WORKDIR /usr/src/app
34
36
 
@@ -49,14 +51,16 @@ COPY package.json bun.lock ./
49
51
 
50
52
  # Install only production dependencies, ignoring any lifecycle scripts (like 'prepare')
51
53
  # that are not needed in the final production image.
52
- RUN bun install --production --frozen-lockfile --ignore-scripts
54
+ RUN --mount=type=cache,target=/root/.bun/install/cache \
55
+ bun install --production --frozen-lockfile --ignore-scripts
53
56
 
54
57
  # Conditionally install OpenTelemetry optional peer dependencies (Tier 3).
55
58
  # These are not bundled by default to keep the base image lean. Enable at build time
56
59
  # with: docker build --build-arg OTEL_ENABLED=true
57
60
  ARG OTEL_ENABLED=true
58
- RUN if [ "$OTEL_ENABLED" = "true" ]; then \
59
- bun add @hono/otel \
61
+ RUN --mount=type=cache,target=/root/.bun/install/cache \
62
+ if [ "$OTEL_ENABLED" = "true" ]; then \
63
+ bun add --omit=dev --ignore-scripts @hono/otel \
60
64
  @opentelemetry/instrumentation-http \
61
65
  @opentelemetry/exporter-metrics-otlp-http \
62
66
  @opentelemetry/exporter-trace-otlp-http \
package/LICENSE CHANGED
@@ -186,7 +186,7 @@ Apache License
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright 2025 Casey Hand @cyanheads
189
+ Copyright 2026 Casey Hand @cyanheads
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-0.1.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/seerr-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/seerr-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.2-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-0.1.2-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/seerr-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/seerr-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.2-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -26,7 +26,7 @@ A workflow MCP server over a self-hosted [Jellyseerr](https://github.com/Fallenb
26
26
  Two properties make it safe to hand an agent:
27
27
 
28
28
  - **Guarded writes.** The one mutating tool (`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.
29
- - **PII/infra redaction.** Raw Jellyseerr payloads carry operator email, Plex/Jellyfin tokens, internal service URLs, and filesystem paths. A single normalization choke point strips all of it before any tool outputrequester objects are projected to `{ id, displayName }`, and root-folder paths are gated behind an explicit `includePaths` flag.
29
+ - **Operator-safe output.** Every payload passes through one normalization layer ([`src/services/seerr/normalizers.ts`](./src/services/seerr/normalizers.ts)) that allow-lists the fields it emits, so operator email, Plex/Jellyfin tokens and media-server IDs, and internal `serviceUrl` hosts never reach the modelrequesters are projected to `{ id, displayName }`. Redaction is always on; filesystem paths are the single opt-in, behind `includePaths`.
30
30
 
31
31
  ## Tools
32
32
 
@@ -36,9 +36,9 @@ Six tools covering the request workflow — discover (`search`) → confirm (`ge
36
36
  |:---|:---|
37
37
  | `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. |
38
38
  | `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. |
39
- | `seerr_list_requests` | List recent requests with status/type/requester filters; echoes the applied filters and decodes every numeric status. |
39
+ | `seerr_list_requests` | List recent requests with status/type/requester filters; echoes the applied filters and decodes every numeric status. Titles are opt-in via `includeTitles`. |
40
40
  | `seerr_request_media` | **Guarded write.** Previews the request payload by default (`mode: preview`); creates the request only on `mode: request` with an elicited confirmation. |
41
- | `seerr_request_status` | Fetch one request by ID — decoded request + media availability (incl. 4K), requester, routing summary, and a state-tuned next-step hint. |
41
+ | `seerr_request_status` | Fetch one request by ID — title, decoded request + media availability (incl. 4K), requester, routing summary, and a state-tuned next-step hint. |
42
42
  | `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`. |
43
43
 
44
44
  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.
@@ -72,7 +72,8 @@ Review recent requests and their lifecycle. Wraps `GET /request`.
72
72
  - Lifecycle `filter` (pending, processing, available, failed, …), `mediaType`, and `requestedById` filters
73
73
  - Sort by created (`added`) or last-changed (`modified`), ascending or descending
74
74
  - `take` / `skip` pagination; the enrichment trailer echoes the filter set the server applied
75
- - Requester is PII-redacted to `{ id, displayName }`; titles aren't on request objects, so they're omitted here — fetch one with `seerr_get_media` when needed
75
+ - Requester is PII-redacted to `{ id, displayName }`
76
+ - Titles aren't on request objects. `includeTitles: true` joins them from the media records — one lookup per _distinct_ title on the page, so a 4K and a non-4K request for one film cost one call. Default is off, keeping the call to a single upstream read. Rows that can't be resolved keep every other field and are disclosed in the notice
76
77
 
77
78
  ---
78
79
 
@@ -103,7 +104,7 @@ Lets an agent reason about request capability and routing without a separate sta
103
104
 
104
105
  | Type | Name | Description |
105
106
  |:---|:---|:---|
106
- | Resource | `seerr://request/{requestId}` | Read-once summary of one request — decoded status + media availability + routing. Mirrors `seerr_request_status`. |
107
+ | Resource | `seerr://request/{requestId}` | Read-once summary of one request — title, decoded status + media availability + routing. Mirrors `seerr_request_status`. |
107
108
 
108
109
  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.
109
110
 
@@ -127,7 +128,6 @@ Jellyseerr-specific:
127
128
 
128
129
  Agent-friendly output:
129
130
 
130
- - **Mandatory PII/infra redaction** — a single normalization choke point projects requester objects to `{ id, displayName }` and drops operator email, Plex/Jellyfin tokens, internal `serviceUrl`, and filesystem paths before any output reaches the model
131
131
  - **Provenance and disclosure** — searches echo the effective query; capped lists disclose truncation; a degraded service leg surfaces a notice instead of silently dropping data
132
132
  - **Typed, actionable errors** — `media_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 prose
133
133
 
@@ -0,0 +1,30 @@
1
+ ---
2
+ summary: "Fixes unencoded search/list query strings, 4K request-status guidance, and season-0 (Specials) requests; consolidates README redaction prose; mcp-ts-core ^0.11.0 and TypeScript 7."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.1 — 2026-08-02
8
+
9
+ ## Changed
10
+
11
+ - **README** — consolidated the PII/infra redaction story to one statement in the intro; dropped the redundant "Agent-friendly output" restatement. ([#1](https://github.com/cyanheads/seerr-mcp-server/issues/1))
12
+ - **devcheck** — new "Dependency Specifiers" check rejects floating specifiers (`latest`, `*`, dist-tags) in `package.json` and `bun.lock`; "Packaging" now also validates plugin-manifest (`.claude-plugin`/`.codex-plugin`) env-var parity.
13
+ - **`bunfig.toml`** — Bun installs now go through `minimumReleaseAge` (3-day supply-chain hold, excluded for `@cyanheads/mcp-ts-core`) and Socket's install-time scanner.
14
+
15
+ ## Fixed
16
+
17
+ - **`seerr_search_media` / `seerr_list_requests`** — query strings are now fully percent-encoded, so a title with a space or any of `! ' ( ) *` no longer 400s against Seerr's reserved-character check. ([#5](https://github.com/cyanheads/seerr-mcp-server/issues/5))
18
+ - **`seerr_request_status` / `seerr://request/{requestId}`** — `stateGuidance` now reads `mediaStatus4k` for `is4k: true` requests instead of the non-4K `mediaStatus`, which commonly sits at `unknown` while the 4K copy is already downloading; `seerr_list_requests` now surfaces `mediaStatus4k` per row too. ([#2](https://github.com/cyanheads/seerr-mcp-server/issues/2))
19
+ - **`seerr_request_media`** — `seasons` accepts `0` (Specials) at the schema; the handler rejects it only when the instance's `enableSpecialEpisodes` is not `true`, via a new `special_episodes_not_enabled` typed error. ([#3](https://github.com/cyanheads/seerr-mcp-server/issues/3))
20
+
21
+ ## Dependencies
22
+
23
+ - `@cyanheads/mcp-ts-core` ^0.10.6 → ^0.11.0
24
+ - `typescript` ^6.0.3 → ^7.0.2
25
+ - `@biomejs/biome` 2.5.0 → 2.5.5
26
+ - `@types/node` 25.9.3 → 26.1.1
27
+ - `vitest` ^4.1.8 → ^4.1.10
28
+ - `tsc-alias` ^1.8.17 → ^1.9.1
29
+ - `ignore` ^7.0.5 → ^7.0.6
30
+ - `@socketsecurity/bun-security-scanner` ^1.1.2 (new)
@@ -0,0 +1,22 @@
1
+ ---
2
+ summary: "Adds opt-in title hydration to seerr_list_requests and unconditional hydration to seerr_request_status/resource; narrows the retry budget for best-effort title lookups; corrects a stale docs/design.md claim about the service's HTTP layer."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.2 — 2026-08-02
8
+
9
+ ## Added
10
+
11
+ - **`seerr_list_requests`** — new opt-in `includeTitles` input (default `false`) resolves each row's title via a join against the media detail endpoint, deduplicated by `(mediaType, tmdbId)` and capped by a 6-wide worker pool (`src/services/seerr/titles.ts`); rows that can't be resolved keep every other field and are disclosed in `notice`. ([#4](https://github.com/cyanheads/seerr-mcp-server/issues/4))
12
+
13
+ ## Changed
14
+
15
+ - **`seerr_request_status`** / **`seerr://request/{requestId}`** — now hydrate `title` unconditionally, one extra read per call, via the same join. ([#4](https://github.com/cyanheads/seerr-mcp-server/issues/4))
16
+ - **`SeerrService.getMovie` / `getTv`** — accept a per-call `options.maxRetries` override; the title join passes `maxRetries: 0` so a failed lookup degrades after a single attempt instead of retrying, since a title is cosmetic and a busy retry would only multiply load on the single-node instance for the same absent result. ([#4](https://github.com/cyanheads/seerr-mcp-server/issues/4))
17
+ - **`docs/design.md`** — corrected the service-layer description: `SeerrService` issues plain `fetch` + `AbortSignal.timeout`, not `fetchWithTimeout` — whose SSRF guard would block the private LAN/Tailscale address and whose thrown error hides the response body the not-found classifier reads.
18
+
19
+ ## Dependencies
20
+
21
+ - `@biomejs/biome` 2.5.5 → 2.5.6
22
+ - `@types/node` 26.1.1 → 26.1.2
@@ -15,9 +15,11 @@ summary: ""
15
15
  # usage. Flagged as `Breaking` in the rollup.
16
16
  breaking: false
17
17
 
18
- # Set `true` if this release contains any security fix. Pairs with the
19
- # `## Security` section below. Flagged as `Security` in the rollup so
20
- # users can triage upgrade urgency at a glance.
18
+ # Set `true` ONLY for a security fix in THIS project's own source code — a
19
+ # vulnerability or hardening in code you ship. A dependency or transitive CVE
20
+ # bump is routine maintenance, NOT a security release: record it under
21
+ # `## Dependencies` (with the advisory ID) and leave this `false`. When true,
22
+ # pairs with the `## Security` section below and flags `Security` in the rollup.
21
23
  security: false
22
24
 
23
25
  # Optional free-form notes for maintenance agents processing this release.
package/dist/index.js CHANGED
File without changes
@@ -11,6 +11,7 @@ export declare const allResourceDefinitions: import("@cyanheads/mcp-ts-core").Re
11
11
  movie: "movie";
12
12
  tv: "tv";
13
13
  }>;
14
+ title: import("zod").ZodOptional<import("zod").ZodString>;
14
15
  tmdbId: import("zod").ZodOptional<import("zod").ZodNumber>;
15
16
  requestStatus: import("zod").ZodObject<{
16
17
  raw: import("zod").ZodNumber;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAAyB,CAAC"}
@@ -2,9 +2,12 @@
2
2
  * @fileoverview seerr://request/{requestId} — read-once request summary. Mirrors
3
3
  * seerr_request_status for clients that support injectable resource context:
4
4
  * decoded request + media status, requester (PII-redacted), routing summary, and a
5
- * state-tuned hint. Reuses the same `projectRequestDetail` choke point as the tool,
6
- * so the redaction is identical. No `list()` request enumeration is the job of
7
- * the filterable seerr_list_requests tool (the tool-only access path).
5
+ * state-tuned hint. Reuses the same `projectRequestDetail` choke point and title
6
+ * join as the tool, so the output is identical and equally redacted. A resource
7
+ * takes no per-read options, so the title is always joined — one request, one
8
+ * extra read — and degrades to absent when the request has no tmdbId or the
9
+ * lookup fails. No `list()` — request enumeration is the job of the filterable
10
+ * seerr_list_requests tool (the tool-only access path).
8
11
  * @module mcp-server/resources/definitions/request.resource
9
12
  */
10
13
  import { z } from '@cyanheads/mcp-ts-core';
@@ -17,6 +20,7 @@ export declare const seerrRequestResource: import("@cyanheads/mcp-ts-core").Reso
17
20
  movie: "movie";
18
21
  tv: "tv";
19
22
  }>;
23
+ title: z.ZodOptional<z.ZodString>;
20
24
  tmdbId: z.ZodOptional<z.ZodNumber>;
21
25
  requestStatus: z.ZodObject<{
22
26
  raw: z.ZodNumber;
@@ -1 +1 @@
1
- {"version":3,"file":"request.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/request.resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAKjE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0D/B,CAAC"}
1
+ {"version":3,"file":"request.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/request.resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAMjE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoE/B,CAAC"}
@@ -2,9 +2,12 @@
2
2
  * @fileoverview seerr://request/{requestId} — read-once request summary. Mirrors
3
3
  * seerr_request_status for clients that support injectable resource context:
4
4
  * decoded request + media status, requester (PII-redacted), routing summary, and a
5
- * state-tuned hint. Reuses the same `projectRequestDetail` choke point as the tool,
6
- * so the redaction is identical. No `list()` request enumeration is the job of
7
- * the filterable seerr_list_requests tool (the tool-only access path).
5
+ * state-tuned hint. Reuses the same `projectRequestDetail` choke point and title
6
+ * join as the tool, so the output is identical and equally redacted. A resource
7
+ * takes no per-read options, so the title is always joined — one request, one
8
+ * extra read — and degrades to absent when the request has no tmdbId or the
9
+ * lookup fails. No `list()` — request enumeration is the job of the filterable
10
+ * seerr_list_requests tool (the tool-only access path).
8
11
  * @module mcp-server/resources/definitions/request.resource
9
12
  */
10
13
  import { resource, z } from '@cyanheads/mcp-ts-core';
@@ -12,6 +15,7 @@ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
12
15
  import { projectRequestDetail } from '../../../services/seerr/normalizers.js';
13
16
  import { getSeerrService } from '../../../services/seerr/seerr-service.js';
14
17
  import { StatusRef } from '../../../services/seerr/status.js';
18
+ import { hydrateRequestTitle } from '../../../services/seerr/titles.js';
15
19
  export const seerrRequestResource = resource('seerr://request/{requestId}', {
16
20
  name: 'seerr-request',
17
21
  title: 'seerr-mcp-server: request',
@@ -26,6 +30,10 @@ export const seerrRequestResource = resource('seerr://request/{requestId}', {
26
30
  output: z.object({
27
31
  requestId: z.number().describe('The request ID.'),
28
32
  mediaType: z.enum(['movie', 'tv']).describe('Movie or TV.'),
33
+ title: z
34
+ .string()
35
+ .optional()
36
+ .describe('Title of the requested media, joined from the media record; absent when the request carries no tmdbId or the lookup failed.'),
29
37
  tmdbId: z.number().optional().describe('TMDB ID of the media.'),
30
38
  requestStatus: StatusRef.describe('Decoded request status {raw,label}.'),
31
39
  mediaStatus: StatusRef.optional().describe('Decoded media availability {raw,label}.'),
@@ -63,8 +71,12 @@ export const seerrRequestResource = resource('seerr://request/{requestId}', {
63
71
  async handler(params, ctx) {
64
72
  const seerr = getSeerrService();
65
73
  const raw = await seerr.getRequest(Number.parseInt(params.requestId, 10), ctx);
66
- ctx.log.debug('Seerr request resource read', { requestId: params.requestId });
67
- return projectRequestDetail(raw);
74
+ const detail = await hydrateRequestTitle(projectRequestDetail(raw), seerr, ctx);
75
+ ctx.log.debug('Seerr request resource read', {
76
+ requestId: params.requestId,
77
+ titleResolved: detail.title !== undefined,
78
+ });
79
+ return detail;
68
80
  },
69
81
  });
70
82
  //# sourceMappingURL=request.resource.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"request.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/request.resource.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"request.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/request.resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE,MAAM,CAAC,MAAM,oBAAoB,GAAG,QAAQ,CAAC,6BAA6B,EAAE;IAC1E,IAAI,EAAE,eAAe;IACrB,KAAK,EAAE,2BAA2B;IAClC,WAAW,EACT,qMAAqM;IACvM,QAAQ,EAAE,kBAAkB;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,KAAK,CAAC,OAAO,CAAC;aACd,QAAQ,CAAC,6EAA6E,CAAC;KAC3F,CAAC;IACF,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QACjD,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC;QAC3D,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,6HAA6H,CAC9H;QACH,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;QAC/D,aAAa,EAAE,SAAS,CAAC,QAAQ,CAAC,qCAAqC,CAAC;QACxE,WAAW,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yCAAyC,CAAC;QACrF,aAAa,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAC1C,sDAAsD,CACvD;QACD,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;QAC3D,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gCAAgC,CAAC;QAClF,WAAW,EAAE,CAAC;aACX,MAAM,CAAC;YACN,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YAC7C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;SAC9E,CAAC;aACD,QAAQ,CAAC,4CAA4C,CAAC;QACzD,OAAO,EAAE,CAAC;aACP,MAAM,CAAC;YACN,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;YACzF,WAAW,EAAE,CAAC;iBACX,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,0DAA0D,CAAC;YACvE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;SAChE,CAAC;aACD,QAAQ,CAAC,wDAAwD,CAAC;QACrE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yBAAyB,CAAC;QACzD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC;QAC7D,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;KAC7F,CAAC;IACF,MAAM,EAAE;QACN;YACE,MAAM,EAAE,mBAAmB;YAC3B,IAAI,EAAE,gBAAgB,CAAC,QAAQ;YAC/B,IAAI,EAAE,oFAAoF;YAC1F,QAAQ,EAAE,+EAA+E;SAC1F;KACF;IAED,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG;QACvB,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;QAChC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QAC/E,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,oBAAoB,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QAChF,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,6BAA6B,EAAE;YAC3C,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,aAAa,EAAE,MAAM,CAAC,KAAK,KAAK,SAAS;SAC1C,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAC,CAAC"}