@cyanheads/seerr-mcp-server 0.1.0
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 +407 -0
- package/CLAUDE.md +407 -0
- package/Dockerfile +121 -0
- package/LICENSE +201 -0
- package/README.md +302 -0
- package/changelog/0.1.x/0.1.0.md +26 -0
- package/changelog/template.md +127 -0
- package/dist/config/server-config.d.ts +27 -0
- package/dist/config/server-config.d.ts.map +1 -0
- package/dist/config/server-config.js +45 -0
- package/dist/config/server-config.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/index.d.ts +47 -0
- package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/index.js +8 -0
- package/dist/mcp-server/resources/definitions/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/request.resource.d.ts +53 -0
- package/dist/mcp-server/resources/definitions/request.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/request.resource.js +70 -0
- package/dist/mcp-server/resources/definitions/request.resource.js.map +1 -0
- package/dist/mcp-server/tools/definitions/get-media.tool.d.ts +66 -0
- package/dist/mcp-server/tools/definitions/get-media.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/get-media.tool.js +197 -0
- package/dist/mcp-server/tools/definitions/get-media.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/index.d.ts +341 -0
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/index.js +22 -0
- package/dist/mcp-server/tools/definitions/index.js.map +1 -0
- package/dist/mcp-server/tools/definitions/list-requests.tool.d.ts +76 -0
- package/dist/mcp-server/tools/definitions/list-requests.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/list-requests.tool.js +187 -0
- package/dist/mcp-server/tools/definitions/list-requests.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/request-media.tool.d.ts +117 -0
- package/dist/mcp-server/tools/definitions/request-media.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/request-media.tool.js +347 -0
- package/dist/mcp-server/tools/definitions/request-media.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/request-status.tool.d.ts +55 -0
- package/dist/mcp-server/tools/definitions/request-status.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/request-status.tool.js +111 -0
- package/dist/mcp-server/tools/definitions/request-status.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/search-media.tool.d.ts +51 -0
- package/dist/mcp-server/tools/definitions/search-media.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/search-media.tool.js +167 -0
- package/dist/mcp-server/tools/definitions/search-media.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/service-options.tool.d.ts +51 -0
- package/dist/mcp-server/tools/definitions/service-options.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/service-options.tool.js +195 -0
- package/dist/mcp-server/tools/definitions/service-options.tool.js.map +1 -0
- package/dist/services/seerr/errors.d.ts +33 -0
- package/dist/services/seerr/errors.d.ts.map +1 -0
- package/dist/services/seerr/errors.js +74 -0
- package/dist/services/seerr/errors.js.map +1 -0
- package/dist/services/seerr/normalizers.d.ts +111 -0
- package/dist/services/seerr/normalizers.d.ts.map +1 -0
- package/dist/services/seerr/normalizers.js +179 -0
- package/dist/services/seerr/normalizers.js.map +1 -0
- package/dist/services/seerr/seerr-service.d.ts +92 -0
- package/dist/services/seerr/seerr-service.d.ts.map +1 -0
- package/dist/services/seerr/seerr-service.js +187 -0
- package/dist/services/seerr/seerr-service.js.map +1 -0
- package/dist/services/seerr/status.d.ts +27 -0
- package/dist/services/seerr/status.d.ts.map +1 -0
- package/dist/services/seerr/status.js +70 -0
- package/dist/services/seerr/status.js.map +1 -0
- package/dist/services/seerr/types.d.ts +221 -0
- package/dist/services/seerr/types.d.ts.map +1 -0
- package/dist/services/seerr/types.js +11 -0
- package/dist/services/seerr/types.js.map +1 -0
- package/package.json +86 -0
- package/server.json +137 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview SeerrService — the typed client for the Seerr REST API v1
|
|
3
|
+
* (`{SEERR_BASE_URL}/api/v1`, `X-Api-Key` auth). One upstream API, one service,
|
|
4
|
+
* init/accessor pattern. Every method wraps a single-attempt fetch + parse in
|
|
5
|
+
* `withRetry` (the retry boundary covers the full pipeline). Not-found-bearing GETs
|
|
6
|
+
* (movie/tv/request) route their non-OK responses through the `errors.ts` classifier
|
|
7
|
+
* so Seerr's idiosyncratic 500/404 not-found bodies become clean domain errors.
|
|
8
|
+
*
|
|
9
|
+
* The instance is a trusted local LAN / Tailscale host, so requests use plain
|
|
10
|
+
* `fetch` (the framework's `fetchWithTimeout` SSRF guard would block the private
|
|
11
|
+
* IP, and the classifier needs body access on non-OK). The base URL and API key
|
|
12
|
+
* never leave this file's request construction — no method returns them.
|
|
13
|
+
* @module services/seerr/seerr-service
|
|
14
|
+
*/
|
|
15
|
+
import { serializationError } from '@cyanheads/mcp-ts-core/errors';
|
|
16
|
+
import { withRetry } from '@cyanheads/mcp-ts-core/utils';
|
|
17
|
+
import { getServerConfig } from '../../config/server-config.js';
|
|
18
|
+
import { throwClassifiedSeerrError } from './errors.js';
|
|
19
|
+
const SERVICE_NAME = 'Seerr';
|
|
20
|
+
const SETTINGS_CACHE_KEY = 'seerr:settings:public';
|
|
21
|
+
const SETTINGS_CACHE_TTL_SECONDS = 300;
|
|
22
|
+
export class SeerrService {
|
|
23
|
+
baseUrl;
|
|
24
|
+
apiKey;
|
|
25
|
+
timeoutMs;
|
|
26
|
+
constructor() {
|
|
27
|
+
const server = getServerConfig();
|
|
28
|
+
this.baseUrl = `${server.baseUrl.replace(/\/+$/, '')}/api/v1`;
|
|
29
|
+
this.apiKey = server.apiKey;
|
|
30
|
+
this.timeoutMs = server.requestTimeoutMs;
|
|
31
|
+
}
|
|
32
|
+
/* ------------------------------------------------------------------ *
|
|
33
|
+
* Search + media detail
|
|
34
|
+
* ------------------------------------------------------------------ */
|
|
35
|
+
/** `GET /search` — title disambiguation. */
|
|
36
|
+
search(params, ctx) {
|
|
37
|
+
const qs = new URLSearchParams({ query: params.query, page: String(params.page) });
|
|
38
|
+
if (params.language)
|
|
39
|
+
qs.set('language', params.language);
|
|
40
|
+
return this.request(`/search?${qs.toString()}`, ctx);
|
|
41
|
+
}
|
|
42
|
+
/** `GET /movie/{id}` — movie detail. 500 "Unable to retrieve movie." → media_not_found. */
|
|
43
|
+
getMovie(tmdbId, ctx) {
|
|
44
|
+
return this.request(`/movie/${tmdbId}`, ctx, { notFoundKind: 'media' });
|
|
45
|
+
}
|
|
46
|
+
/** `GET /tv/{id}` — TV detail. */
|
|
47
|
+
getTv(tmdbId, ctx) {
|
|
48
|
+
return this.request(`/tv/${tmdbId}`, ctx, { notFoundKind: 'media' });
|
|
49
|
+
}
|
|
50
|
+
/** `GET /tv/{id}/season/{n}` — episode list for one season. */
|
|
51
|
+
getSeason(tmdbId, seasonNumber, ctx) {
|
|
52
|
+
return this.request(`/tv/${tmdbId}/season/${seasonNumber}`, ctx, {
|
|
53
|
+
notFoundKind: 'media',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/* ------------------------------------------------------------------ *
|
|
57
|
+
* Requests
|
|
58
|
+
* ------------------------------------------------------------------ */
|
|
59
|
+
/** `GET /request` — list requests with filters. */
|
|
60
|
+
listRequests(params, ctx) {
|
|
61
|
+
const qs = new URLSearchParams({
|
|
62
|
+
take: String(params.take),
|
|
63
|
+
skip: String(params.skip),
|
|
64
|
+
filter: params.filter,
|
|
65
|
+
sort: params.sort,
|
|
66
|
+
sortDirection: params.sortDirection,
|
|
67
|
+
});
|
|
68
|
+
if (params.mediaType && params.mediaType !== 'all')
|
|
69
|
+
qs.set('mediaType', params.mediaType);
|
|
70
|
+
if (typeof params.requestedById === 'number')
|
|
71
|
+
qs.set('requestedBy', String(params.requestedById));
|
|
72
|
+
return this.request(`/request?${qs.toString()}`, ctx);
|
|
73
|
+
}
|
|
74
|
+
/** `GET /request/{id}` — single request. 404 "Request not found." → request_not_found. */
|
|
75
|
+
getRequest(requestId, ctx) {
|
|
76
|
+
return this.request(`/request/${requestId}`, ctx, {
|
|
77
|
+
notFoundKind: 'request',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** `POST /request` — create a media request. The only write in the surface. */
|
|
81
|
+
createRequest(body, ctx) {
|
|
82
|
+
return this.request('/request', ctx, { method: 'POST', body });
|
|
83
|
+
}
|
|
84
|
+
/* ------------------------------------------------------------------ *
|
|
85
|
+
* Services + settings
|
|
86
|
+
* ------------------------------------------------------------------ */
|
|
87
|
+
/** `GET /service/radarr` — Radarr service list. */
|
|
88
|
+
getRadarrServices(ctx) {
|
|
89
|
+
return this.request('/service/radarr', ctx);
|
|
90
|
+
}
|
|
91
|
+
/** `GET /service/radarr/{id}` — Radarr profiles + root folders. */
|
|
92
|
+
getRadarrDetail(serverId, ctx) {
|
|
93
|
+
return this.request(`/service/radarr/${serverId}`, ctx);
|
|
94
|
+
}
|
|
95
|
+
/** `GET /service/sonarr` — Sonarr service list. */
|
|
96
|
+
getSonarrServices(ctx) {
|
|
97
|
+
return this.request('/service/sonarr', ctx);
|
|
98
|
+
}
|
|
99
|
+
/** `GET /service/sonarr/{id}` — Sonarr profiles + root folders. */
|
|
100
|
+
getSonarrDetail(serverId, ctx) {
|
|
101
|
+
return this.request(`/service/sonarr/${serverId}`, ctx);
|
|
102
|
+
}
|
|
103
|
+
/** `GET /status` — Seerr version. */
|
|
104
|
+
getStatus(ctx) {
|
|
105
|
+
return this.request('/status', ctx);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* `GET /settings/public` — capability flags. Cached in `ctx.state` with a short
|
|
109
|
+
* TTL: capability checks read it on most calls (preview validation, search/get
|
|
110
|
+
* 4K decoding) but instance config changes rarely. Cache failures are non-fatal.
|
|
111
|
+
*/
|
|
112
|
+
async getPublicSettings(ctx, options = {}) {
|
|
113
|
+
if (!options.forceRefresh) {
|
|
114
|
+
const cached = await ctx.state.get(SETTINGS_CACHE_KEY).catch(() => null);
|
|
115
|
+
if (cached)
|
|
116
|
+
return cached;
|
|
117
|
+
}
|
|
118
|
+
const settings = await this.request('/settings/public', ctx);
|
|
119
|
+
await ctx.state
|
|
120
|
+
.set(SETTINGS_CACHE_KEY, settings, { ttl: SETTINGS_CACHE_TTL_SECONDS })
|
|
121
|
+
.catch(() => { });
|
|
122
|
+
return settings;
|
|
123
|
+
}
|
|
124
|
+
/* ------------------------------------------------------------------ *
|
|
125
|
+
* Core request pipeline
|
|
126
|
+
* ------------------------------------------------------------------ */
|
|
127
|
+
/**
|
|
128
|
+
* Single Seerr request: fetch (single attempt) + status check + JSON parse, all
|
|
129
|
+
* inside `withRetry`. Non-OK responses route through the not-found classifier
|
|
130
|
+
* (when `notFoundKind` is set) or the framework's status mapping. The retry
|
|
131
|
+
* boundary wraps the whole pipeline so a transient parse failure also retries.
|
|
132
|
+
*/
|
|
133
|
+
request(path, ctx, options = {}) {
|
|
134
|
+
const url = `${this.baseUrl}${path}`;
|
|
135
|
+
const method = options.method ?? 'GET';
|
|
136
|
+
return withRetry(async () => {
|
|
137
|
+
const headers = {
|
|
138
|
+
'X-Api-Key': this.apiKey,
|
|
139
|
+
Accept: 'application/json',
|
|
140
|
+
};
|
|
141
|
+
if (options.body !== undefined)
|
|
142
|
+
headers['Content-Type'] = 'application/json';
|
|
143
|
+
const response = await fetch(url, {
|
|
144
|
+
method,
|
|
145
|
+
headers,
|
|
146
|
+
...(options.body !== undefined ? { body: JSON.stringify(options.body) } : {}),
|
|
147
|
+
signal: AbortSignal.any([ctx.signal, AbortSignal.timeout(this.timeoutMs)]),
|
|
148
|
+
});
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
await throwClassifiedSeerrError(response, options.notFoundKind ?? 'media', SERVICE_NAME);
|
|
151
|
+
}
|
|
152
|
+
const text = await response.text();
|
|
153
|
+
if (text.length === 0)
|
|
154
|
+
return undefined;
|
|
155
|
+
try {
|
|
156
|
+
return JSON.parse(text);
|
|
157
|
+
}
|
|
158
|
+
catch (cause) {
|
|
159
|
+
throw serializationError(`${SERVICE_NAME} returned a non-JSON response.`, { path }, { cause });
|
|
160
|
+
}
|
|
161
|
+
}, {
|
|
162
|
+
operation: `SeerrService.request ${method} ${path}`,
|
|
163
|
+
context: { requestId: ctx.requestId, timestamp: ctx.timestamp },
|
|
164
|
+
baseDelayMs: options.baseDelayMs ?? 300,
|
|
165
|
+
signal: ctx.signal,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/* --- Init/accessor pattern --- */
|
|
170
|
+
let _service;
|
|
171
|
+
/**
|
|
172
|
+
* Initialize the singleton SeerrService — called from `createApp`'s `setup()`.
|
|
173
|
+
* Config and storage are accepted to match the framework's init signature; the
|
|
174
|
+
* service reads its own env config via `getServerConfig()` and uses tenant-scoped
|
|
175
|
+
* `ctx.state` (not the raw storage handle) for the settings cache.
|
|
176
|
+
*/
|
|
177
|
+
export function initSeerrService(_config, _storage) {
|
|
178
|
+
_service = new SeerrService();
|
|
179
|
+
}
|
|
180
|
+
/** Accessor for the SeerrService singleton. Throws if `initSeerrService` was not called. */
|
|
181
|
+
export function getSeerrService() {
|
|
182
|
+
if (!_service) {
|
|
183
|
+
throw new Error('SeerrService not initialized — call initSeerrService() in setup()');
|
|
184
|
+
}
|
|
185
|
+
return _service;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=seerr-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seerr-service.js","sourceRoot":"","sources":["../../../src/services/seerr/seerr-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pure status decoders for Seerr's numeric status enums. Every tool
|
|
3
|
+
* output that carries a request status, media availability, or media-server type
|
|
4
|
+
* emits a decoded `{ raw, label }` object via these helpers — never a bare number
|
|
5
|
+
* or bare string. Unknown codes degrade to `{ raw, label: 'unrecognized' }` rather
|
|
6
|
+
* than throwing, so a future Seerr status code still surfaces its raw value.
|
|
7
|
+
* @module services/seerr/status
|
|
8
|
+
*/
|
|
9
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
10
|
+
/**
|
|
11
|
+
* Shared Zod shape for a decoded status. Reused across every tool output and the
|
|
12
|
+
* resource so the `{ raw, label }` contract is identical everywhere.
|
|
13
|
+
*/
|
|
14
|
+
export declare const StatusRef: z.ZodObject<{
|
|
15
|
+
raw: z.ZodNumber;
|
|
16
|
+
label: z.ZodString;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
export type DecodedStatus = z.infer<typeof StatusRef>;
|
|
19
|
+
/** Decode a `MediaRequest.status` numeric code to `{ raw, label }`. */
|
|
20
|
+
export declare function decodeRequestStatus(raw: number): DecodedStatus;
|
|
21
|
+
/** Decode a `MediaInfo.status` / `status4k` numeric code to `{ raw, label }`. */
|
|
22
|
+
export declare function decodeMediaStatus(raw: number): DecodedStatus;
|
|
23
|
+
/** Decode a `mediaServerType` setting code to its label (plex | jellyfin | emby | unrecognized). */
|
|
24
|
+
export declare function decodeMediaServer(raw: number): string;
|
|
25
|
+
/** Render a decoded status for `format()` output as `label (raw)` — surfaces both halves so format-parity holds. */
|
|
26
|
+
export declare function statusText(status: DecodedStatus): string;
|
|
27
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/services/seerr/status.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAE3C;;;GAGG;AACH,eAAO,MAAM,SAAS;;;iBAK4D,CAAC;AAEnF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAuCtD,uEAAuE;AACvE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAE9D;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAE5D;AAED,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,oHAAoH;AACpH,wBAAgB,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAExD"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pure status decoders for Seerr's numeric status enums. Every tool
|
|
3
|
+
* output that carries a request status, media availability, or media-server type
|
|
4
|
+
* emits a decoded `{ raw, label }` object via these helpers — never a bare number
|
|
5
|
+
* or bare string. Unknown codes degrade to `{ raw, label: 'unrecognized' }` rather
|
|
6
|
+
* than throwing, so a future Seerr status code still surfaces its raw value.
|
|
7
|
+
* @module services/seerr/status
|
|
8
|
+
*/
|
|
9
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
10
|
+
/**
|
|
11
|
+
* Shared Zod shape for a decoded status. Reused across every tool output and the
|
|
12
|
+
* resource so the `{ raw, label }` contract is identical everywhere.
|
|
13
|
+
*/
|
|
14
|
+
export const StatusRef = z
|
|
15
|
+
.object({
|
|
16
|
+
raw: z.number().describe('Raw numeric status code from the Seerr API.'),
|
|
17
|
+
label: z.string().describe('Human-readable decoded label for the status code.'),
|
|
18
|
+
})
|
|
19
|
+
.describe('Decoded status: the raw numeric code plus its human-readable label.');
|
|
20
|
+
/**
|
|
21
|
+
* `MediaRequest.status` codes (request lifecycle). Matches Jellyseerr/Seerr's
|
|
22
|
+
* `MediaRequestStatus` enum. Live Seerr 3.3.0 emits 4 (failed) and 5 (completed)
|
|
23
|
+
* on real requests — over half the requests on a working instance are status 5 —
|
|
24
|
+
* so decoding only 1-3 mislabels every completed/failed request as `unrecognized`.
|
|
25
|
+
*/
|
|
26
|
+
const REQUEST_STATUS_LABELS = {
|
|
27
|
+
1: 'pending',
|
|
28
|
+
2: 'approved',
|
|
29
|
+
3: 'declined',
|
|
30
|
+
4: 'failed',
|
|
31
|
+
5: 'completed',
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* `MediaInfo.status` / `MediaInfo.status4k` codes (availability lifecycle). Matches
|
|
35
|
+
* Jellyseerr/Seerr's `MediaStatus` enum: 6 is `blocklisted` and 7 is `deleted`
|
|
36
|
+
* (BLOCKLISTED was inserted at 6, shifting DELETED to 7) — mapping 6 to `deleted`
|
|
37
|
+
* mislabels a blocklisted item and leaves an actually-deleted item `unrecognized`.
|
|
38
|
+
*/
|
|
39
|
+
const MEDIA_STATUS_LABELS = {
|
|
40
|
+
1: 'unknown',
|
|
41
|
+
2: 'pending',
|
|
42
|
+
3: 'processing',
|
|
43
|
+
4: 'partially_available',
|
|
44
|
+
5: 'available',
|
|
45
|
+
6: 'blocklisted',
|
|
46
|
+
7: 'deleted',
|
|
47
|
+
};
|
|
48
|
+
/** `mediaServerType` setting codes. */
|
|
49
|
+
const MEDIA_SERVER_LABELS = {
|
|
50
|
+
1: 'plex',
|
|
51
|
+
2: 'jellyfin',
|
|
52
|
+
3: 'emby',
|
|
53
|
+
};
|
|
54
|
+
/** Decode a `MediaRequest.status` numeric code to `{ raw, label }`. */
|
|
55
|
+
export function decodeRequestStatus(raw) {
|
|
56
|
+
return { raw, label: REQUEST_STATUS_LABELS[raw] ?? 'unrecognized' };
|
|
57
|
+
}
|
|
58
|
+
/** Decode a `MediaInfo.status` / `status4k` numeric code to `{ raw, label }`. */
|
|
59
|
+
export function decodeMediaStatus(raw) {
|
|
60
|
+
return { raw, label: MEDIA_STATUS_LABELS[raw] ?? 'unrecognized' };
|
|
61
|
+
}
|
|
62
|
+
/** Decode a `mediaServerType` setting code to its label (plex | jellyfin | emby | unrecognized). */
|
|
63
|
+
export function decodeMediaServer(raw) {
|
|
64
|
+
return MEDIA_SERVER_LABELS[raw] ?? 'unrecognized';
|
|
65
|
+
}
|
|
66
|
+
/** Render a decoded status for `format()` output as `label (raw)` — surfaces both halves so format-parity holds. */
|
|
67
|
+
export function statusText(status) {
|
|
68
|
+
return `${status.label} (${status.raw})`;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/services/seerr/status.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAE3C;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC;KACvB,MAAM,CAAC;IACN,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;IACvE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mDAAmD,CAAC;CAChF,CAAC;KACD,QAAQ,CAAC,qEAAqE,CAAC,CAAC;AAInF;;;;;GAKG;AACH,MAAM,qBAAqB,GAA2B;IACpD,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,UAAU;IACb,CAAC,EAAE,UAAU;IACb,CAAC,EAAE,QAAQ;IACX,CAAC,EAAE,WAAW;CACf,CAAC;AAEF;;;;;GAKG;AACH,MAAM,mBAAmB,GAA2B;IAClD,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,YAAY;IACf,CAAC,EAAE,qBAAqB;IACxB,CAAC,EAAE,WAAW;IACd,CAAC,EAAE,aAAa;IAChB,CAAC,EAAE,SAAS;CACb,CAAC;AAEF,uCAAuC;AACvC,MAAM,mBAAmB,GAA2B;IAClD,CAAC,EAAE,MAAM;IACT,CAAC,EAAE,UAAU;IACb,CAAC,EAAE,MAAM;CACV,CAAC;AAEF,uEAAuE;AACvE,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,qBAAqB,CAAC,GAAG,CAAC,IAAI,cAAc,EAAE,CAAC;AACtE,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,mBAAmB,CAAC,GAAG,CAAC,IAAI,cAAc,EAAE,CAAC;AACpE,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,mBAAmB,CAAC,GAAG,CAAC,IAAI,cAAc,CAAC;AACpD,CAAC;AAED,oHAAoH;AACpH,MAAM,UAAU,UAAU,CAAC,MAAqB;IAC9C,OAAO,GAAG,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,GAAG,GAAG,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Raw upstream Seerr API types (hand-written from the OpenAPI spec +
|
|
3
|
+
* live read-only probes against Seerr 3.3.0) and the normalized domain types this
|
|
4
|
+
* server exposes. Raw types default to optional unless the live payload guarantees
|
|
5
|
+
* presence — third-party APIs omit fields rather than nulling them, and over-strict
|
|
6
|
+
* raw types either fail validation or invent facts. The domain types are the
|
|
7
|
+
* PII-redacted projections produced by `normalizers.ts`.
|
|
8
|
+
* @module services/seerr/types
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Raw Seerr `User` object. Nested in `requestedBy` AND `modifiedBy` on requests.
|
|
12
|
+
* Carries heavy PII — email, Plex/Jellyfin tokens and IDs, quotas, avatar,
|
|
13
|
+
* permissions bitfield. `normalizers.ts` projects it to `{ id, displayName }`;
|
|
14
|
+
* nothing else here may reach tool output.
|
|
15
|
+
*/
|
|
16
|
+
export interface RawUser {
|
|
17
|
+
avatar?: string;
|
|
18
|
+
displayName?: string;
|
|
19
|
+
email?: string;
|
|
20
|
+
id: number;
|
|
21
|
+
jellyfinUserId?: string;
|
|
22
|
+
jellyfinUsername?: string;
|
|
23
|
+
permissions?: number;
|
|
24
|
+
plexId?: number;
|
|
25
|
+
plexUsername?: string;
|
|
26
|
+
username?: string;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Raw `MediaInfo` nested on search results, movie/TV detail, and request `media`.
|
|
31
|
+
* Leaks `serviceUrl`/`serviceUrl4k` (internal host:port), `jellyfinMediaId`,
|
|
32
|
+
* `ratingKey`, etc. — only `status`/`status4k`/`tmdbId`/`mediaType` are read.
|
|
33
|
+
* `requests[]` is present ONLY on movie/TV detail, never on search results.
|
|
34
|
+
*/
|
|
35
|
+
export interface RawMediaInfo {
|
|
36
|
+
id?: number;
|
|
37
|
+
mediaType?: string;
|
|
38
|
+
requests?: RawMediaRequest[];
|
|
39
|
+
serviceUrl?: string;
|
|
40
|
+
serviceUrl4k?: string;
|
|
41
|
+
status?: number;
|
|
42
|
+
status4k?: number;
|
|
43
|
+
tmdbId?: number;
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Raw `MediaRequest`. The media-type discriminator is `type` (not `mediaType`).
|
|
48
|
+
* `profileName` is present at the list endpoint but ABSENT at `GET /request/{id}`.
|
|
49
|
+
* Both `requestedBy` and `modifiedBy` are full `RawUser` objects.
|
|
50
|
+
*/
|
|
51
|
+
export interface RawMediaRequest {
|
|
52
|
+
createdAt?: string;
|
|
53
|
+
id: number;
|
|
54
|
+
is4k?: boolean;
|
|
55
|
+
isAutoRequest?: boolean;
|
|
56
|
+
languageProfileId?: number | null;
|
|
57
|
+
media?: RawMediaInfo;
|
|
58
|
+
modifiedBy?: RawUser;
|
|
59
|
+
profileId?: number;
|
|
60
|
+
profileName?: string | null;
|
|
61
|
+
requestedBy?: RawUser;
|
|
62
|
+
rootFolder?: string | null;
|
|
63
|
+
seasonCount?: number;
|
|
64
|
+
seasons?: Array<{
|
|
65
|
+
seasonNumber?: number;
|
|
66
|
+
} | number>;
|
|
67
|
+
serverId?: number;
|
|
68
|
+
status?: number;
|
|
69
|
+
type?: string;
|
|
70
|
+
updatedAt?: string;
|
|
71
|
+
[key: string]: unknown;
|
|
72
|
+
}
|
|
73
|
+
/** Raw search-result hit (`MovieResult` | `TvResult`); `person` is filtered out upstream of this. */
|
|
74
|
+
export interface RawSearchResult {
|
|
75
|
+
firstAirDate?: string;
|
|
76
|
+
id: number;
|
|
77
|
+
mediaInfo?: RawMediaInfo;
|
|
78
|
+
mediaType?: string;
|
|
79
|
+
name?: string;
|
|
80
|
+
overview?: string;
|
|
81
|
+
releaseDate?: string;
|
|
82
|
+
title?: string;
|
|
83
|
+
voteAverage?: number;
|
|
84
|
+
[key: string]: unknown;
|
|
85
|
+
}
|
|
86
|
+
/** Raw `/search` response envelope (flat — distinct from the request-list `pageInfo` envelope). */
|
|
87
|
+
export interface RawSearchResponse {
|
|
88
|
+
page?: number;
|
|
89
|
+
results?: RawSearchResult[];
|
|
90
|
+
totalPages?: number;
|
|
91
|
+
totalResults?: number;
|
|
92
|
+
}
|
|
93
|
+
/** Raw season summary on TV detail (`seasons[]`). */
|
|
94
|
+
export interface RawSeason {
|
|
95
|
+
airDate?: string | null;
|
|
96
|
+
episodeCount?: number;
|
|
97
|
+
id?: number;
|
|
98
|
+
name?: string;
|
|
99
|
+
overview?: string;
|
|
100
|
+
seasonNumber?: number;
|
|
101
|
+
[key: string]: unknown;
|
|
102
|
+
}
|
|
103
|
+
/** Raw episode on a season detail (`/tv/{id}/season/{n}` → `episodes[]`). */
|
|
104
|
+
export interface RawEpisode {
|
|
105
|
+
airDate?: string | null;
|
|
106
|
+
episodeNumber?: number;
|
|
107
|
+
id?: number;
|
|
108
|
+
name?: string;
|
|
109
|
+
overview?: string;
|
|
110
|
+
[key: string]: unknown;
|
|
111
|
+
}
|
|
112
|
+
/** Raw movie detail (`GET /movie/{id}`). */
|
|
113
|
+
export interface RawMovieDetail {
|
|
114
|
+
id: number;
|
|
115
|
+
mediaInfo?: RawMediaInfo;
|
|
116
|
+
overview?: string;
|
|
117
|
+
releaseDate?: string;
|
|
118
|
+
runtime?: number;
|
|
119
|
+
status?: string;
|
|
120
|
+
title?: string;
|
|
121
|
+
[key: string]: unknown;
|
|
122
|
+
}
|
|
123
|
+
/** Raw TV detail (`GET /tv/{id}`). Live API uses `numberOfSeasons` (plural). */
|
|
124
|
+
export interface RawTvDetail {
|
|
125
|
+
firstAirDate?: string;
|
|
126
|
+
id: number;
|
|
127
|
+
mediaInfo?: RawMediaInfo;
|
|
128
|
+
name?: string;
|
|
129
|
+
numberOfSeasons?: number;
|
|
130
|
+
overview?: string;
|
|
131
|
+
seasons?: RawSeason[];
|
|
132
|
+
status?: string;
|
|
133
|
+
[key: string]: unknown;
|
|
134
|
+
}
|
|
135
|
+
/** Raw season detail (`GET /tv/{id}/season/{n}`). */
|
|
136
|
+
export interface RawSeasonDetail {
|
|
137
|
+
episodes?: RawEpisode[];
|
|
138
|
+
id?: number;
|
|
139
|
+
name?: string;
|
|
140
|
+
seasonNumber?: number;
|
|
141
|
+
[key: string]: unknown;
|
|
142
|
+
}
|
|
143
|
+
/** Raw request-list envelope (`{ pageInfo, results, serviceErrors }`). */
|
|
144
|
+
export interface RawRequestListResponse {
|
|
145
|
+
pageInfo?: {
|
|
146
|
+
page?: number;
|
|
147
|
+
pages?: number;
|
|
148
|
+
pageSize?: number;
|
|
149
|
+
results?: number;
|
|
150
|
+
};
|
|
151
|
+
results?: RawMediaRequest[];
|
|
152
|
+
}
|
|
153
|
+
/** Raw `/settings/public`. Drives capability + validation; `vapidPublic`/`plexClientIdentifier` are stripped from output. */
|
|
154
|
+
export interface RawPublicSettings {
|
|
155
|
+
applicationTitle?: string;
|
|
156
|
+
enableSpecialEpisodes?: boolean;
|
|
157
|
+
mediaServerType?: number;
|
|
158
|
+
movie4kEnabled?: boolean;
|
|
159
|
+
partialRequestsEnabled?: boolean;
|
|
160
|
+
series4kEnabled?: boolean;
|
|
161
|
+
[key: string]: unknown;
|
|
162
|
+
}
|
|
163
|
+
/** Raw `/status`. */
|
|
164
|
+
export interface RawStatus {
|
|
165
|
+
version?: string;
|
|
166
|
+
[key: string]: unknown;
|
|
167
|
+
}
|
|
168
|
+
/** Raw Radarr/Sonarr list entry. Leaks `activeDirectory` (+ `activeAnimeDirectory` on Sonarr). */
|
|
169
|
+
export interface RawServiceListEntry {
|
|
170
|
+
activeAnimeDirectory?: string;
|
|
171
|
+
activeAnimeProfileId?: number;
|
|
172
|
+
activeDirectory?: string;
|
|
173
|
+
activeProfileId?: number;
|
|
174
|
+
id: number;
|
|
175
|
+
is4k?: boolean;
|
|
176
|
+
isDefault?: boolean;
|
|
177
|
+
name?: string;
|
|
178
|
+
[key: string]: unknown;
|
|
179
|
+
}
|
|
180
|
+
/** Raw quality profile (`{ id, name }`) from a service detail endpoint. */
|
|
181
|
+
export interface RawServiceProfile {
|
|
182
|
+
id: number;
|
|
183
|
+
name?: string;
|
|
184
|
+
[key: string]: unknown;
|
|
185
|
+
}
|
|
186
|
+
/** Raw root folder from a service detail endpoint (`freeSpace`, not `freeSpaceBytes`). */
|
|
187
|
+
export interface RawRootFolder {
|
|
188
|
+
freeSpace?: number;
|
|
189
|
+
id?: number;
|
|
190
|
+
path?: string;
|
|
191
|
+
[key: string]: unknown;
|
|
192
|
+
}
|
|
193
|
+
/** Raw service detail (`/service/radarr/{id}` | `/service/sonarr/{id}`). `profiles` is an array. */
|
|
194
|
+
export interface RawServiceDetail {
|
|
195
|
+
languageProfiles?: unknown;
|
|
196
|
+
profiles?: RawServiceProfile[];
|
|
197
|
+
rootFolders?: RawRootFolder[];
|
|
198
|
+
server?: RawServiceListEntry;
|
|
199
|
+
[key: string]: unknown;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Body submitted to `POST /request`. Also the `payload` surfaced in preview output,
|
|
203
|
+
* so `is4k` is always present (the tool sets it explicitly). `userId` is
|
|
204
|
+
* intentionally never set — the request is made as the API key's user.
|
|
205
|
+
*/
|
|
206
|
+
export interface CreateRequestBody {
|
|
207
|
+
is4k: boolean;
|
|
208
|
+
languageProfileId?: number;
|
|
209
|
+
mediaId: number;
|
|
210
|
+
mediaType: 'movie' | 'tv';
|
|
211
|
+
profileId?: number;
|
|
212
|
+
rootFolder?: string;
|
|
213
|
+
seasons?: number[] | 'all';
|
|
214
|
+
serverId?: number;
|
|
215
|
+
}
|
|
216
|
+
/** PII-safe requester/modifier projection. The ONLY shape of a Seerr user that may reach output. */
|
|
217
|
+
export interface DomainUser {
|
|
218
|
+
displayName: string;
|
|
219
|
+
id: number;
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/seerr/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Raw upstream Seerr API types (hand-written from the OpenAPI spec +
|
|
3
|
+
* live read-only probes against Seerr 3.3.0) and the normalized domain types this
|
|
4
|
+
* server exposes. Raw types default to optional unless the live payload guarantees
|
|
5
|
+
* presence — third-party APIs omit fields rather than nulling them, and over-strict
|
|
6
|
+
* raw types either fail validation or invent facts. The domain types are the
|
|
7
|
+
* PII-redacted projections produced by `normalizers.ts`.
|
|
8
|
+
* @module services/seerr/types
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/seerr/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cyanheads/seerr-mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Search Jellyseerr/Overseerr, check availability, and create guarded media requests via MCP. STDIO or Streamable HTTP.",
|
|
5
|
+
"mcpName": "io.github.cyanheads/seerr-mcp-server",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"seerr-mcp-server": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"changelog/",
|
|
14
|
+
"dist/",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"CLAUDE.md",
|
|
18
|
+
"AGENTS.md",
|
|
19
|
+
"Dockerfile",
|
|
20
|
+
"server.json"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "bun run scripts/build.ts",
|
|
24
|
+
"rebuild": "bun run scripts/clean.ts && bun run scripts/build.ts",
|
|
25
|
+
"clean": "bun run scripts/clean.ts",
|
|
26
|
+
"devcheck": "bun run scripts/devcheck.ts",
|
|
27
|
+
"audit:refresh": "rm -f bun.lock && bun install && bun audit",
|
|
28
|
+
"tree": "bun run scripts/tree.ts",
|
|
29
|
+
"list-skills": "bun run scripts/list-skills.ts",
|
|
30
|
+
"format": "biome check --write .",
|
|
31
|
+
"format:unsafe": "biome check --write --unsafe .",
|
|
32
|
+
"lint:mcp": "bun run scripts/lint-mcp.ts",
|
|
33
|
+
"lint:packaging": "bun run scripts/lint-packaging.ts",
|
|
34
|
+
"bundle": "bun run build && npx -y @anthropic-ai/mcpb pack . dist/seerr-mcp-server.mcpb && bun run scripts/clean-mcpb.ts dist/seerr-mcp-server.mcpb",
|
|
35
|
+
"changelog:build": "bun run scripts/build-changelog.ts",
|
|
36
|
+
"changelog:check": "bun run scripts/build-changelog.ts --check",
|
|
37
|
+
"release:github": "bun run scripts/release-github.ts",
|
|
38
|
+
"publish-mcp": "mcp-publisher login github -token \"$(security find-generic-password -a \"$USER\" -s mcp-publisher-github-pat -w)\" && mcp-publisher publish",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"start": "node dist/index.js",
|
|
41
|
+
"start:stdio": "MCP_TRANSPORT_TYPE=stdio node dist/index.js",
|
|
42
|
+
"start:http": "MCP_TRANSPORT_TYPE=http node dist/index.js"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"mcp",
|
|
46
|
+
"mcp-server",
|
|
47
|
+
"model-context-protocol",
|
|
48
|
+
"jellyseerr",
|
|
49
|
+
"overseerr",
|
|
50
|
+
"seerr",
|
|
51
|
+
"radarr",
|
|
52
|
+
"sonarr",
|
|
53
|
+
"media-requests"
|
|
54
|
+
],
|
|
55
|
+
"author": "cyanheads <casey@caseyjhand.com> (https://github.com/cyanheads/seerr-mcp-server#readme)",
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "git+https://github.com/cyanheads/seerr-mcp-server.git"
|
|
59
|
+
},
|
|
60
|
+
"homepage": "https://github.com/cyanheads/seerr-mcp-server#readme",
|
|
61
|
+
"bugs": {
|
|
62
|
+
"url": "https://github.com/cyanheads/seerr-mcp-server/issues"
|
|
63
|
+
},
|
|
64
|
+
"license": "Apache-2.0",
|
|
65
|
+
"engines": {
|
|
66
|
+
"bun": ">=1.3.0",
|
|
67
|
+
"node": ">=24.0.0"
|
|
68
|
+
},
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"access": "public"
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"@cyanheads/mcp-ts-core": "^0.10.6",
|
|
74
|
+
"pino-pretty": "^13.1.3",
|
|
75
|
+
"zod": "^4.4.3"
|
|
76
|
+
},
|
|
77
|
+
"devDependencies": {
|
|
78
|
+
"@biomejs/biome": "2.5.0",
|
|
79
|
+
"@types/node": "25.9.3",
|
|
80
|
+
"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"
|
|
85
|
+
}
|
|
86
|
+
}
|