@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.
- package/AGENTS.md +19 -16
- package/CLAUDE.md +19 -16
- package/Dockerfile +11 -7
- package/LICENSE +1 -1
- package/README.md +7 -7
- package/changelog/0.1.x/0.1.1.md +30 -0
- package/changelog/0.1.x/0.1.2.md +22 -0
- package/changelog/template.md +5 -3
- package/dist/index.js +0 -0
- package/dist/mcp-server/resources/definitions/index.d.ts +1 -0
- package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/resources/definitions/request.resource.d.ts +7 -3
- package/dist/mcp-server/resources/definitions/request.resource.d.ts.map +1 -1
- package/dist/mcp-server/resources/definitions/request.resource.js +17 -5
- package/dist/mcp-server/resources/definitions/request.resource.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +152 -142
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/list-requests.tool.d.ts +16 -9
- package/dist/mcp-server/tools/definitions/list-requests.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/list-requests.tool.js +74 -11
- package/dist/mcp-server/tools/definitions/list-requests.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/request-media.tool.d.ts +7 -2
- package/dist/mcp-server/tools/definitions/request-media.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/request-media.tool.js +18 -4
- package/dist/mcp-server/tools/definitions/request-media.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/request-status.tool.d.ts +4 -2
- package/dist/mcp-server/tools/definitions/request-status.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/request-status.tool.js +11 -4
- package/dist/mcp-server/tools/definitions/request-status.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-media.tool.d.ts +1 -1
- package/dist/services/seerr/normalizers.d.ts +5 -1
- package/dist/services/seerr/normalizers.d.ts.map +1 -1
- package/dist/services/seerr/normalizers.js +21 -7
- package/dist/services/seerr/normalizers.js.map +1 -1
- package/dist/services/seerr/seerr-service.d.ts +12 -2
- package/dist/services/seerr/seerr-service.d.ts.map +1 -1
- package/dist/services/seerr/seerr-service.js +42 -16
- package/dist/services/seerr/seerr-service.js.map +1 -1
- package/dist/services/seerr/titles.d.ts +47 -0
- package/dist/services/seerr/titles.d.ts.map +1 -0
- package/dist/services/seerr/titles.js +96 -0
- package/dist/services/seerr/titles.js.map +1 -0
- package/package.json +11 -9
- 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.
|
|
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": "
|
|
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.
|
|
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.
|
|
79
|
-
"@
|
|
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.
|
|
82
|
-
"tsc-alias": "^1.
|
|
83
|
-
"typescript": "^
|
|
84
|
-
"vitest": "^4.1.
|
|
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.
|
|
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.
|
|
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.
|
|
64
|
+
"version": "0.1.2",
|
|
65
65
|
"packageArguments": [
|
|
66
66
|
{
|
|
67
67
|
"type": "positional",
|