@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
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @fileoverview Title hydration for request-shaped output. Seerr's `MediaRequest`
3
+ * objects carry no title field — only `media.tmdbId` — so a readable request row
4
+ * costs one extra `GET /movie/{id}` or `GET /tv/{id}` per distinct title. This
5
+ * module owns that join and the three properties it needs:
6
+ *
7
+ * - **De-duplication.** A 4K and a non-4K request for one film, or several
8
+ * seasons of one show, share a `(mediaType, tmdbId)` and resolve once.
9
+ * - **Bounded fan-out.** Lookups run through a fixed-size worker pool, never
10
+ * one-shot `Promise.all` over an entire page. Each leg is single-attempt, so
11
+ * the pool's width is also the real ceiling on upstream calls.
12
+ * - **Degradation.** A lookup that throws, returns an empty title, or has no
13
+ * `tmdbId` to look up yields no title and no error — the row keeps every
14
+ * other field. Callers disclose the count; nothing here throws.
15
+ * @module services/seerr/titles
16
+ */
17
+ /**
18
+ * Simultaneous detail lookups per hydration pass. Seerr is a single-node app
19
+ * proxying TMDB on a self-hosted LAN host, so a 100-row page must not open 100
20
+ * sockets against it; 6 matches the per-origin connection budget browsers have
21
+ * long used against one host, and drains a full page in ~17 sequential waves.
22
+ */
23
+ const TITLE_LOOKUP_CONCURRENCY = 6;
24
+ /**
25
+ * A title is cosmetic, so a leg that fails is absorbed — which makes the default
26
+ * retry budget actively harmful here: a transient-classified detail read would
27
+ * burn four upstream calls and seconds of backoff to produce the same absent
28
+ * title, occupying a worker and multiplying load on the single-node instance the
29
+ * pool exists to protect. One attempt, then degrade.
30
+ */
31
+ const BEST_EFFORT = { maxRetries: 0 };
32
+ /** Map key for a resolved title. */
33
+ export function titleKeyOf(mediaType, tmdbId) {
34
+ return `${mediaType}:${tmdbId}`;
35
+ }
36
+ /**
37
+ * Resolve titles for a batch of `(mediaType, tmdbId)` pairs. Duplicates collapse
38
+ * before any fetch, so N rows sharing a title cost one call. Never throws — a
39
+ * failed leg is absorbed, leaving its key absent from `titles`.
40
+ */
41
+ export async function resolveTitles(keys, seerr, ctx) {
42
+ const distinct = new Map();
43
+ for (const key of keys)
44
+ distinct.set(titleKeyOf(key.mediaType, key.tmdbId), key);
45
+ const entries = [...distinct];
46
+ const titles = new Map();
47
+ let cursor = 0;
48
+ /**
49
+ * Pull-based worker: each pulls the next index and runs to exhaustion, so a
50
+ * slow lookup delays only itself rather than stalling a fixed-size chunk.
51
+ */
52
+ const worker = async () => {
53
+ for (let entry = entries[cursor++]; entry !== undefined; entry = entries[cursor++]) {
54
+ const [key, { mediaType, tmdbId }] = entry;
55
+ const title = await fetchTitle(mediaType, tmdbId, seerr, ctx);
56
+ if (title)
57
+ titles.set(key, title);
58
+ }
59
+ };
60
+ await Promise.all(Array.from({ length: Math.min(TITLE_LOOKUP_CONCURRENCY, entries.length) }, worker));
61
+ return { titles, uniqueKeys: entries.length };
62
+ }
63
+ /**
64
+ * Attach a resolved title to a single projected request detail. Returns the
65
+ * detail untouched when there is no `tmdbId` to look up or the lookup fails —
66
+ * the shared path behind seerr_request_status and the request resource, both of
67
+ * which hydrate unconditionally (one request, one extra call).
68
+ */
69
+ export async function hydrateRequestTitle(detail, seerr, ctx) {
70
+ if (typeof detail.tmdbId !== 'number')
71
+ return detail;
72
+ const title = await fetchTitle(detail.mediaType, detail.tmdbId, seerr, ctx);
73
+ return title ? { ...detail, title } : detail;
74
+ }
75
+ /**
76
+ * One detail fetch reduced to its title. `RawMovieDetail.title` / `RawTvDetail.name`
77
+ * are the same fields seerr_get_media reads. Upstream failures (including Seerr's
78
+ * 500-flavored not-found) resolve to `undefined` rather than throwing.
79
+ */
80
+ async function fetchTitle(mediaType, tmdbId, seerr, ctx) {
81
+ try {
82
+ const title = mediaType === 'tv'
83
+ ? (await seerr.getTv(tmdbId, ctx, BEST_EFFORT)).name
84
+ : (await seerr.getMovie(tmdbId, ctx, BEST_EFFORT)).title;
85
+ return title?.trim() || undefined;
86
+ }
87
+ catch (error) {
88
+ ctx.log.debug('Seerr title lookup failed', {
89
+ mediaType,
90
+ tmdbId,
91
+ error: error instanceof Error ? error.message : String(error),
92
+ });
93
+ return;
94
+ }
95
+ }
96
+ //# sourceMappingURL=titles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"titles.js","sourceRoot":"","sources":["../../../src/services/seerr/titles.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/seerr-mcp-server",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Search Jellyseerr/Overseerr, check availability, and create guarded media requests via MCP. STDIO or Streamable HTTP.",
5
5
  "mcpName": "io.github.cyanheads/seerr-mcp-server",
6
6
  "type": "module",
@@ -52,7 +52,7 @@
52
52
  "sonarr",
53
53
  "media-requests"
54
54
  ],
55
- "author": "cyanheads <casey@caseyjhand.com> (https://github.com/cyanheads/seerr-mcp-server#readme)",
55
+ "author": "Casey Hand <casey@caseyjhand.com> (https://caseyjhand.com)",
56
56
  "repository": {
57
57
  "type": "git",
58
58
  "url": "git+https://github.com/cyanheads/seerr-mcp-server.git"
@@ -62,6 +62,7 @@
62
62
  "url": "https://github.com/cyanheads/seerr-mcp-server/issues"
63
63
  },
64
64
  "license": "Apache-2.0",
65
+ "packageManager": "bun@1.3.14",
65
66
  "engines": {
66
67
  "bun": ">=1.3.0",
67
68
  "node": ">=24.0.0"
@@ -70,17 +71,18 @@
70
71
  "access": "public"
71
72
  },
72
73
  "dependencies": {
73
- "@cyanheads/mcp-ts-core": "^0.10.6",
74
+ "@cyanheads/mcp-ts-core": "^0.11.0",
74
75
  "pino-pretty": "^13.1.3",
75
76
  "zod": "^4.4.3"
76
77
  },
77
78
  "devDependencies": {
78
- "@biomejs/biome": "2.5.0",
79
- "@types/node": "25.9.3",
79
+ "@biomejs/biome": "2.5.6",
80
+ "@socketsecurity/bun-security-scanner": "^1.1.2",
81
+ "@types/node": "26.1.2",
80
82
  "depcheck": "^1.4.7",
81
- "ignore": "^7.0.5",
82
- "tsc-alias": "^1.8.17",
83
- "typescript": "^6.0.3",
84
- "vitest": "^4.1.8"
83
+ "ignore": "^7.0.6",
84
+ "tsc-alias": "^1.9.1",
85
+ "typescript": "^7.0.2",
86
+ "vitest": "^4.1.10"
85
87
  }
86
88
  }
package/server.json CHANGED
@@ -6,14 +6,14 @@
6
6
  "url": "https://github.com/cyanheads/seerr-mcp-server",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.1.0",
9
+ "version": "0.1.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "registryBaseUrl": "https://registry.npmjs.org",
14
14
  "identifier": "@cyanheads/seerr-mcp-server",
15
15
  "runtimeHint": "bun",
16
- "version": "0.1.0",
16
+ "version": "0.1.2",
17
17
  "packageArguments": [
18
18
  {
19
19
  "type": "positional",
@@ -61,7 +61,7 @@
61
61
  "registryBaseUrl": "https://registry.npmjs.org",
62
62
  "identifier": "@cyanheads/seerr-mcp-server",
63
63
  "runtimeHint": "bun",
64
- "version": "0.1.0",
64
+ "version": "0.1.2",
65
65
  "packageArguments": [
66
66
  {
67
67
  "type": "positional",