@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.
Files changed (73) hide show
  1. package/AGENTS.md +407 -0
  2. package/CLAUDE.md +407 -0
  3. package/Dockerfile +121 -0
  4. package/LICENSE +201 -0
  5. package/README.md +302 -0
  6. package/changelog/0.1.x/0.1.0.md +26 -0
  7. package/changelog/template.md +127 -0
  8. package/dist/config/server-config.d.ts +27 -0
  9. package/dist/config/server-config.d.ts.map +1 -0
  10. package/dist/config/server-config.js +45 -0
  11. package/dist/config/server-config.js.map +1 -0
  12. package/dist/index.d.ts +9 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +24 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/mcp-server/resources/definitions/index.d.ts +47 -0
  17. package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -0
  18. package/dist/mcp-server/resources/definitions/index.js +8 -0
  19. package/dist/mcp-server/resources/definitions/index.js.map +1 -0
  20. package/dist/mcp-server/resources/definitions/request.resource.d.ts +53 -0
  21. package/dist/mcp-server/resources/definitions/request.resource.d.ts.map +1 -0
  22. package/dist/mcp-server/resources/definitions/request.resource.js +70 -0
  23. package/dist/mcp-server/resources/definitions/request.resource.js.map +1 -0
  24. package/dist/mcp-server/tools/definitions/get-media.tool.d.ts +66 -0
  25. package/dist/mcp-server/tools/definitions/get-media.tool.d.ts.map +1 -0
  26. package/dist/mcp-server/tools/definitions/get-media.tool.js +197 -0
  27. package/dist/mcp-server/tools/definitions/get-media.tool.js.map +1 -0
  28. package/dist/mcp-server/tools/definitions/index.d.ts +341 -0
  29. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -0
  30. package/dist/mcp-server/tools/definitions/index.js +22 -0
  31. package/dist/mcp-server/tools/definitions/index.js.map +1 -0
  32. package/dist/mcp-server/tools/definitions/list-requests.tool.d.ts +76 -0
  33. package/dist/mcp-server/tools/definitions/list-requests.tool.d.ts.map +1 -0
  34. package/dist/mcp-server/tools/definitions/list-requests.tool.js +187 -0
  35. package/dist/mcp-server/tools/definitions/list-requests.tool.js.map +1 -0
  36. package/dist/mcp-server/tools/definitions/request-media.tool.d.ts +117 -0
  37. package/dist/mcp-server/tools/definitions/request-media.tool.d.ts.map +1 -0
  38. package/dist/mcp-server/tools/definitions/request-media.tool.js +347 -0
  39. package/dist/mcp-server/tools/definitions/request-media.tool.js.map +1 -0
  40. package/dist/mcp-server/tools/definitions/request-status.tool.d.ts +55 -0
  41. package/dist/mcp-server/tools/definitions/request-status.tool.d.ts.map +1 -0
  42. package/dist/mcp-server/tools/definitions/request-status.tool.js +111 -0
  43. package/dist/mcp-server/tools/definitions/request-status.tool.js.map +1 -0
  44. package/dist/mcp-server/tools/definitions/search-media.tool.d.ts +51 -0
  45. package/dist/mcp-server/tools/definitions/search-media.tool.d.ts.map +1 -0
  46. package/dist/mcp-server/tools/definitions/search-media.tool.js +167 -0
  47. package/dist/mcp-server/tools/definitions/search-media.tool.js.map +1 -0
  48. package/dist/mcp-server/tools/definitions/service-options.tool.d.ts +51 -0
  49. package/dist/mcp-server/tools/definitions/service-options.tool.d.ts.map +1 -0
  50. package/dist/mcp-server/tools/definitions/service-options.tool.js +195 -0
  51. package/dist/mcp-server/tools/definitions/service-options.tool.js.map +1 -0
  52. package/dist/services/seerr/errors.d.ts +33 -0
  53. package/dist/services/seerr/errors.d.ts.map +1 -0
  54. package/dist/services/seerr/errors.js +74 -0
  55. package/dist/services/seerr/errors.js.map +1 -0
  56. package/dist/services/seerr/normalizers.d.ts +111 -0
  57. package/dist/services/seerr/normalizers.d.ts.map +1 -0
  58. package/dist/services/seerr/normalizers.js +179 -0
  59. package/dist/services/seerr/normalizers.js.map +1 -0
  60. package/dist/services/seerr/seerr-service.d.ts +92 -0
  61. package/dist/services/seerr/seerr-service.d.ts.map +1 -0
  62. package/dist/services/seerr/seerr-service.js +187 -0
  63. package/dist/services/seerr/seerr-service.js.map +1 -0
  64. package/dist/services/seerr/status.d.ts +27 -0
  65. package/dist/services/seerr/status.d.ts.map +1 -0
  66. package/dist/services/seerr/status.js +70 -0
  67. package/dist/services/seerr/status.js.map +1 -0
  68. package/dist/services/seerr/types.d.ts +221 -0
  69. package/dist/services/seerr/types.d.ts.map +1 -0
  70. package/dist/services/seerr/types.js +11 -0
  71. package/dist/services/seerr/types.js.map +1 -0
  72. package/package.json +86 -0
  73. package/server.json +137 -0
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @fileoverview Server-specific environment configuration for seerr-mcp-server.
3
+ * Lazy-parsed Zod schema, separate from the framework's core config. Maps schema
4
+ * paths to env var names via `parseEnvConfig` so startup errors name the variable
5
+ * (`SEERR_BASE_URL is missing`) rather than the schema path.
6
+ * @module config/server-config
7
+ */
8
+ import { z } from '@cyanheads/mcp-ts-core';
9
+ /**
10
+ * Seerr connection + behavior config. `baseUrl` and `apiKey` are required — the
11
+ * server cannot reach the instance without them. The base URL must omit the
12
+ * `/api/v1` suffix (the service appends it) and carry no trailing slash.
13
+ */
14
+ declare const ServerConfigSchema: z.ZodObject<{
15
+ baseUrl: z.ZodString;
16
+ apiKey: z.ZodString;
17
+ requestTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
18
+ }, z.core.$strip>;
19
+ export type SeerrServerConfig = z.infer<typeof ServerConfigSchema>;
20
+ /**
21
+ * Lazily parse and cache the server config from the environment. Lazy parsing
22
+ * keeps top-level `process.env` reads out of module load so the Workers runtime
23
+ * (which injects env at request time) stays compatible.
24
+ */
25
+ export declare function getServerConfig(): SeerrServerConfig;
26
+ export {};
27
+ //# sourceMappingURL=server-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAG3C;;;;GAIG;AACH,QAAA,MAAM,kBAAkB;;;;iBAiBtB,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAInE;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,iBAAiB,CAOnD"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @fileoverview Server-specific environment configuration for seerr-mcp-server.
3
+ * Lazy-parsed Zod schema, separate from the framework's core config. Maps schema
4
+ * paths to env var names via `parseEnvConfig` so startup errors name the variable
5
+ * (`SEERR_BASE_URL is missing`) rather than the schema path.
6
+ * @module config/server-config
7
+ */
8
+ import { z } from '@cyanheads/mcp-ts-core';
9
+ import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
10
+ /**
11
+ * Seerr connection + behavior config. `baseUrl` and `apiKey` are required — the
12
+ * server cannot reach the instance without them. The base URL must omit the
13
+ * `/api/v1` suffix (the service appends it) and carry no trailing slash.
14
+ */
15
+ const ServerConfigSchema = z.object({
16
+ baseUrl: z
17
+ .string()
18
+ .url()
19
+ .describe('Seerr instance base URL, e.g. http://host:5055. No /api/v1 suffix, no trailing slash.'),
20
+ apiKey: z
21
+ .string()
22
+ .min(1)
23
+ .describe('Seerr API key (Settings → General → API Key). Sent as the X-Api-Key header.'),
24
+ requestTimeoutMs: z.coerce
25
+ .number()
26
+ .int()
27
+ .positive()
28
+ .default(15000)
29
+ .describe('Per-request HTTP timeout in milliseconds.'),
30
+ });
31
+ let _config;
32
+ /**
33
+ * Lazily parse and cache the server config from the environment. Lazy parsing
34
+ * keeps top-level `process.env` reads out of module load so the Workers runtime
35
+ * (which injects env at request time) stays compatible.
36
+ */
37
+ export function getServerConfig() {
38
+ _config ??= parseEnvConfig(ServerConfigSchema, {
39
+ baseUrl: 'SEERR_BASE_URL',
40
+ apiKey: 'SEERR_API_KEY',
41
+ requestTimeoutMs: 'SEERR_REQUEST_TIMEOUT_MS',
42
+ });
43
+ return _config;
44
+ }
45
+ //# sourceMappingURL=server-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D;;;;GAIG;AACH,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,CACP,uFAAuF,CACxF;IACH,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,6EAA6E,CAAC;IAC1F,gBAAgB,EAAE,CAAC,CAAC,MAAM;SACvB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,QAAQ,EAAE;SACV,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CAAC,2CAA2C,CAAC;CACzD,CAAC,CAAC;AAIH,IAAI,OAAsC,CAAC;AAE3C;;;;GAIG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,cAAc,CAAC,kBAAkB,EAAE;QAC7C,OAAO,EAAE,gBAAgB;QACzB,MAAM,EAAE,eAAe;QACvB,gBAAgB,EAAE,0BAA0B;KAC7C,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview seerr-mcp-server MCP server entry point. Wires the SeerrService in
4
+ * setup() and registers the six tools + one resource. Identity is name + title
5
+ * only (the hyphenated machine name) — description derives from package.json.
6
+ * @module index
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG"}
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview seerr-mcp-server MCP server entry point. Wires the SeerrService in
4
+ * setup() and registers the six tools + one resource. Identity is name + title
5
+ * only (the hyphenated machine name) — description derives from package.json.
6
+ * @module index
7
+ */
8
+ import { createApp } from '@cyanheads/mcp-ts-core';
9
+ import { allResourceDefinitions } from './mcp-server/resources/definitions/index.js';
10
+ import { allToolDefinitions } from './mcp-server/tools/definitions/index.js';
11
+ import { initSeerrService } from './services/seerr/seerr-service.js';
12
+ await createApp({
13
+ name: 'seerr-mcp-server',
14
+ title: 'seerr-mcp-server',
15
+ tools: allToolDefinitions,
16
+ resources: allResourceDefinitions,
17
+ instructions: 'Local Seerr request workflow. Search first (seerr_search_media), confirm the exact title ' +
18
+ '(seerr_get_media), then request via seerr_request_media — which defaults to mode:preview and ' +
19
+ 'only writes on mode:request. "Download X" means "create a Seerr request for X"; never bypass to Radarr/Sonarr.',
20
+ setup(core) {
21
+ initSeerrService(core.config, core.storage);
22
+ },
23
+ });
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAErE,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,kBAAkB;IACzB,KAAK,EAAE,kBAAkB;IACzB,SAAS,EAAE,sBAAsB;IACjC,YAAY,EACV,2FAA2F;QAC3F,+FAA+F;QAC/F,gHAAgH;IAClH,KAAK,CAAC,IAAI;QACR,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @fileoverview Barrel collecting every resource definition into
3
+ * `allResourceDefinitions` for `createApp()`.
4
+ * @module mcp-server/resources/definitions/index
5
+ */
6
+ export declare const allResourceDefinitions: import("@cyanheads/mcp-ts-core").ResourceDefinition<import("zod").ZodObject<{
7
+ requestId: import("zod").ZodString;
8
+ }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
9
+ requestId: import("zod").ZodNumber;
10
+ mediaType: import("zod").ZodEnum<{
11
+ movie: "movie";
12
+ tv: "tv";
13
+ }>;
14
+ tmdbId: import("zod").ZodOptional<import("zod").ZodNumber>;
15
+ requestStatus: import("zod").ZodObject<{
16
+ raw: import("zod").ZodNumber;
17
+ label: import("zod").ZodString;
18
+ }, import("zod/v4/core").$strip>;
19
+ mediaStatus: import("zod").ZodOptional<import("zod").ZodObject<{
20
+ raw: import("zod").ZodNumber;
21
+ label: import("zod").ZodString;
22
+ }, import("zod/v4/core").$strip>>;
23
+ mediaStatus4k: import("zod").ZodOptional<import("zod").ZodObject<{
24
+ raw: import("zod").ZodNumber;
25
+ label: import("zod").ZodString;
26
+ }, import("zod/v4/core").$strip>>;
27
+ is4k: import("zod").ZodBoolean;
28
+ seasons: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodNumber>>;
29
+ requestedBy: import("zod").ZodObject<{
30
+ id: import("zod").ZodNumber;
31
+ displayName: import("zod").ZodString;
32
+ }, import("zod/v4/core").$strip>;
33
+ routing: import("zod").ZodObject<{
34
+ serverId: import("zod").ZodOptional<import("zod").ZodNumber>;
35
+ profileName: import("zod").ZodOptional<import("zod").ZodString>;
36
+ is4k: import("zod").ZodBoolean;
37
+ }, import("zod/v4/core").$strip>;
38
+ createdAt: import("zod").ZodString;
39
+ updatedAt: import("zod").ZodString;
40
+ stateGuidance: import("zod").ZodOptional<import("zod").ZodString>;
41
+ }, import("zod/v4/core").$strip>, readonly [{
42
+ readonly reason: "request_not_found";
43
+ readonly code: import("@cyanheads/mcp-ts-core/errors").JsonRpcErrorCode.NotFound;
44
+ readonly when: "No request exists with the given ID (Seerr returns HTTP 404 \"Request not found.\").";
45
+ readonly recovery: "List requests with seerr_list_requests to find a valid requestId, then retry.";
46
+ }]>[];
47
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @fileoverview Barrel collecting every resource definition into
3
+ * `allResourceDefinitions` for `createApp()`.
4
+ * @module mcp-server/resources/definitions/index
5
+ */
6
+ import { seerrRequestResource } from './request.resource.js';
7
+ export const allResourceDefinitions = [seerrRequestResource];
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,oBAAoB,CAAC,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @fileoverview seerr://request/{requestId} — read-once request summary. Mirrors
3
+ * seerr_request_status for clients that support injectable resource context:
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).
8
+ * @module mcp-server/resources/definitions/request.resource
9
+ */
10
+ import { z } from '@cyanheads/mcp-ts-core';
11
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
12
+ export declare const seerrRequestResource: import("@cyanheads/mcp-ts-core").ResourceDefinition<z.ZodObject<{
13
+ requestId: z.ZodString;
14
+ }, z.core.$strip>, z.ZodObject<{
15
+ requestId: z.ZodNumber;
16
+ mediaType: z.ZodEnum<{
17
+ movie: "movie";
18
+ tv: "tv";
19
+ }>;
20
+ tmdbId: z.ZodOptional<z.ZodNumber>;
21
+ requestStatus: z.ZodObject<{
22
+ raw: z.ZodNumber;
23
+ label: z.ZodString;
24
+ }, z.core.$strip>;
25
+ mediaStatus: z.ZodOptional<z.ZodObject<{
26
+ raw: z.ZodNumber;
27
+ label: z.ZodString;
28
+ }, z.core.$strip>>;
29
+ mediaStatus4k: z.ZodOptional<z.ZodObject<{
30
+ raw: z.ZodNumber;
31
+ label: z.ZodString;
32
+ }, z.core.$strip>>;
33
+ is4k: z.ZodBoolean;
34
+ seasons: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
35
+ requestedBy: z.ZodObject<{
36
+ id: z.ZodNumber;
37
+ displayName: z.ZodString;
38
+ }, z.core.$strip>;
39
+ routing: z.ZodObject<{
40
+ serverId: z.ZodOptional<z.ZodNumber>;
41
+ profileName: z.ZodOptional<z.ZodString>;
42
+ is4k: z.ZodBoolean;
43
+ }, z.core.$strip>;
44
+ createdAt: z.ZodString;
45
+ updatedAt: z.ZodString;
46
+ stateGuidance: z.ZodOptional<z.ZodString>;
47
+ }, z.core.$strip>, readonly [{
48
+ readonly reason: "request_not_found";
49
+ readonly code: JsonRpcErrorCode.NotFound;
50
+ readonly when: "No request exists with the given ID (Seerr returns HTTP 404 \"Request not found.\").";
51
+ readonly recovery: "List requests with seerr_list_requests to find a valid requestId, then retry.";
52
+ }]>;
53
+ //# sourceMappingURL=request.resource.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @fileoverview seerr://request/{requestId} — read-once request summary. Mirrors
3
+ * seerr_request_status for clients that support injectable resource context:
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).
8
+ * @module mcp-server/resources/definitions/request.resource
9
+ */
10
+ import { resource, z } from '@cyanheads/mcp-ts-core';
11
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
12
+ import { projectRequestDetail } from '../../../services/seerr/normalizers.js';
13
+ import { getSeerrService } from '../../../services/seerr/seerr-service.js';
14
+ import { StatusRef } from '../../../services/seerr/status.js';
15
+ export const seerrRequestResource = resource('seerr://request/{requestId}', {
16
+ name: 'seerr-request',
17
+ title: 'seerr-mcp-server: request',
18
+ description: 'Read-once summary of one Seerr media request by ID — decoded request status, media availability, requester, and routing. Mirrors seerr_request_status for clients that inject resources as context.',
19
+ mimeType: 'application/json',
20
+ params: z.object({
21
+ requestId: z
22
+ .string()
23
+ .regex(/^\d+$/)
24
+ .describe('Numeric Seerr request ID (from seerr_request_media or seerr_list_requests).'),
25
+ }),
26
+ output: z.object({
27
+ requestId: z.number().describe('The request ID.'),
28
+ mediaType: z.enum(['movie', 'tv']).describe('Movie or TV.'),
29
+ tmdbId: z.number().optional().describe('TMDB ID of the media.'),
30
+ requestStatus: StatusRef.describe('Decoded request status {raw,label}.'),
31
+ mediaStatus: StatusRef.optional().describe('Decoded media availability {raw,label}.'),
32
+ mediaStatus4k: StatusRef.optional().describe('Decoded 4K availability {raw,label} when applicable.'),
33
+ is4k: z.boolean().describe('Whether this is a 4K request.'),
34
+ seasons: z.array(z.number()).optional().describe('Requested season numbers (TV).'),
35
+ requestedBy: z
36
+ .object({
37
+ id: z.number().describe('Requester user ID.'),
38
+ displayName: z.string().describe('Requester display name (no email/tokens).'),
39
+ })
40
+ .describe('Who requested it — id + display name only.'),
41
+ routing: z
42
+ .object({
43
+ serverId: z.number().optional().describe('Radarr/Sonarr server ID handling the request.'),
44
+ profileName: z
45
+ .string()
46
+ .optional()
47
+ .describe('Quality profile name; null when not set on this request.'),
48
+ is4k: z.boolean().describe('Whether routed to the 4K service.'),
49
+ })
50
+ .describe('Routing summary — names/IDs only, no filesystem paths.'),
51
+ createdAt: z.string().describe('ISO creation timestamp.'),
52
+ updatedAt: z.string().describe('ISO last-updated timestamp.'),
53
+ stateGuidance: z.string().optional().describe('Next-step hint tuned to the current status.'),
54
+ }),
55
+ errors: [
56
+ {
57
+ reason: 'request_not_found',
58
+ code: JsonRpcErrorCode.NotFound,
59
+ when: 'No request exists with the given ID (Seerr returns HTTP 404 "Request not found.").',
60
+ recovery: 'List requests with seerr_list_requests to find a valid requestId, then retry.',
61
+ },
62
+ ],
63
+ async handler(params, ctx) {
64
+ const seerr = getSeerrService();
65
+ 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);
68
+ },
69
+ });
70
+ //# sourceMappingURL=request.resource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/request.resource.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @fileoverview seerr_get_media — confirm the exact title before a write. Wraps
3
+ * `GET /movie/{id}` or `GET /tv/{id}`, optionally `GET /tv/{id}/season/{n}` for an
4
+ * episode list. Returns availability + whether a request already exists (derived
5
+ * from `mediaInfo.requests[]`, most-recent first). A missing TMDB id surfaces as a
6
+ * clean `media_not_found` (Seerr's raw HTTP 500 is classified in the service layer).
7
+ * @module mcp-server/tools/definitions/get-media.tool
8
+ */
9
+ import { z } from '@cyanheads/mcp-ts-core';
10
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
11
+ export declare const getMediaTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
12
+ mediaType: z.ZodEnum<{
13
+ movie: "movie";
14
+ tv: "tv";
15
+ }>;
16
+ tmdbId: z.ZodNumber;
17
+ seasonNumber: z.ZodOptional<z.ZodNumber>;
18
+ }, z.core.$strip>, z.ZodObject<{
19
+ tmdbId: z.ZodNumber;
20
+ mediaType: z.ZodEnum<{
21
+ movie: "movie";
22
+ tv: "tv";
23
+ }>;
24
+ title: z.ZodString;
25
+ year: z.ZodOptional<z.ZodNumber>;
26
+ overview: z.ZodOptional<z.ZodString>;
27
+ runtimeMinutes: z.ZodOptional<z.ZodNumber>;
28
+ productionStatus: z.ZodOptional<z.ZodString>;
29
+ availability: z.ZodObject<{
30
+ tracked: z.ZodBoolean;
31
+ status: z.ZodObject<{
32
+ raw: z.ZodNumber;
33
+ label: z.ZodString;
34
+ }, z.core.$strip>;
35
+ status4k: z.ZodOptional<z.ZodObject<{
36
+ raw: z.ZodNumber;
37
+ label: z.ZodString;
38
+ }, z.core.$strip>>;
39
+ openRequest: z.ZodOptional<z.ZodObject<{
40
+ requestId: z.ZodNumber;
41
+ status: z.ZodObject<{
42
+ raw: z.ZodNumber;
43
+ label: z.ZodString;
44
+ }, z.core.$strip>;
45
+ is4k: z.ZodBoolean;
46
+ }, z.core.$strip>>;
47
+ }, z.core.$strip>;
48
+ seasons: z.ZodOptional<z.ZodArray<z.ZodObject<{
49
+ seasonNumber: z.ZodNumber;
50
+ name: z.ZodString;
51
+ episodeCount: z.ZodNumber;
52
+ airDate: z.ZodNullable<z.ZodString>;
53
+ }, z.core.$strip>>>;
54
+ episodes: z.ZodOptional<z.ZodArray<z.ZodObject<{
55
+ episodeNumber: z.ZodNumber;
56
+ name: z.ZodString;
57
+ airDate: z.ZodNullable<z.ZodString>;
58
+ overview: z.ZodOptional<z.ZodString>;
59
+ }, z.core.$strip>>>;
60
+ }, z.core.$strip>, readonly [{
61
+ readonly reason: "media_not_found";
62
+ readonly code: JsonRpcErrorCode.NotFound;
63
+ readonly when: "The TMDB ID does not resolve to a movie/show (Seerr returns HTTP 500 \"Unable to retrieve movie.\").";
64
+ readonly recovery: "Call seerr_search_media to find the correct tmdbId, then retry with the exact ID and matching mediaType.";
65
+ }], undefined>;
66
+ //# sourceMappingURL=get-media.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-media.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-media.tool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAiBjE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAsMvB,CAAC"}
@@ -0,0 +1,197 @@
1
+ /**
2
+ * @fileoverview seerr_get_media — confirm the exact title before a write. Wraps
3
+ * `GET /movie/{id}` or `GET /tv/{id}`, optionally `GET /tv/{id}/season/{n}` for an
4
+ * episode list. Returns availability + whether a request already exists (derived
5
+ * from `mediaInfo.requests[]`, most-recent first). A missing TMDB id surfaces as a
6
+ * clean `media_not_found` (Seerr's raw HTTP 500 is classified in the service layer).
7
+ * @module mcp-server/tools/definitions/get-media.tool
8
+ */
9
+ import { tool, z } from '@cyanheads/mcp-ts-core';
10
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
11
+ import { redactAvailability, redactEpisode, redactOpenRequest, redactSeason, } from '../../../services/seerr/normalizers.js';
12
+ import { getSeerrService } from '../../../services/seerr/seerr-service.js';
13
+ import { StatusRef, statusText } from '../../../services/seerr/status.js';
14
+ /** Parse a 4-digit year from an ISO-ish date string. */
15
+ function yearOf(date) {
16
+ if (!date)
17
+ return;
18
+ const year = Number.parseInt(date.slice(0, 4), 10);
19
+ return Number.isFinite(year) ? year : undefined;
20
+ }
21
+ export const getMediaTool = tool('seerr_get_media', {
22
+ title: 'seerr-mcp-server: get media',
23
+ description: "Fetch exact movie or show details by TMDB ID and media type to confirm the right title before any write. Returns availability and any existing open request. For TV, omit seasonNumber for a per-season summary, or pass a seasonNumber to fetch that season's episode list. Get the tmdbId from seerr_search_media first.",
24
+ annotations: { readOnlyHint: true, openWorldHint: true },
25
+ input: z.object({
26
+ mediaType: z
27
+ .enum(['movie', 'tv'])
28
+ .describe('Whether tmdbId refers to a movie or TV show. Determines which detail endpoint is queried.'),
29
+ tmdbId: z
30
+ .number()
31
+ .int()
32
+ .positive()
33
+ .describe('TMDB ID from seerr_search_media (the results[].tmdbId field).'),
34
+ seasonNumber: z
35
+ .number()
36
+ .int()
37
+ .min(0)
38
+ .optional()
39
+ .describe("TV only: fetch this season's episode list. Season 0 is Specials. Omit for a show-level summary with per-season counts."),
40
+ }),
41
+ output: z.object({
42
+ tmdbId: z.number().describe('TMDB ID for chaining to seerr_request_media.'),
43
+ mediaType: z.enum(['movie', 'tv']).describe('Movie or TV.'),
44
+ title: z.string().describe('Title (movie) or name (TV).'),
45
+ year: z.number().optional().describe('Release/first-air year; omitted when unknown.'),
46
+ overview: z.string().optional().describe('Synopsis.'),
47
+ runtimeMinutes: z.number().optional().describe('Movie runtime in minutes (movies only).'),
48
+ productionStatus: z
49
+ .string()
50
+ .optional()
51
+ .describe('TMDB production status, e.g. "Released", "Returning Series".'),
52
+ availability: z
53
+ .object({
54
+ tracked: z.boolean().describe('True when Seerr tracks this title.'),
55
+ status: StatusRef.describe('Decoded availability {raw,label}.'),
56
+ status4k: StatusRef.optional().describe('Decoded 4K availability when 4K is enabled.'),
57
+ openRequest: z
58
+ .object({
59
+ requestId: z.number().describe('Existing request ID — pass to seerr_request_status.'),
60
+ status: StatusRef.describe('Decoded request status {raw,label}.'),
61
+ is4k: z.boolean().describe('Whether the open request is for 4K.'),
62
+ })
63
+ .optional()
64
+ .describe('Most recent existing request for this title, if any — avoids duplicate requests.'),
65
+ })
66
+ .describe('Seerr availability + existing-request context for this title.'),
67
+ seasons: z
68
+ .array(z
69
+ .object({
70
+ seasonNumber: z.number().describe('Season number; 0 = Specials.'),
71
+ name: z.string().describe('Season name.'),
72
+ episodeCount: z.number().describe('Episode count in this season.'),
73
+ airDate: z
74
+ .string()
75
+ .nullable()
76
+ .describe('Season air date (ISO) or null when unannounced.'),
77
+ })
78
+ .describe('A per-season summary entry.'))
79
+ .optional()
80
+ .describe('TV only: per-season summary (omitted for movies).'),
81
+ episodes: z
82
+ .array(z
83
+ .object({
84
+ episodeNumber: z.number().describe('Episode number within the season.'),
85
+ name: z.string().describe('Episode title.'),
86
+ airDate: z.string().nullable().describe('Air date (ISO) or null.'),
87
+ overview: z.string().optional().describe('Episode synopsis.'),
88
+ })
89
+ .describe('An episode entry within the requested season.'))
90
+ .optional()
91
+ .describe('TV only: episode list, present only when seasonNumber was provided.'),
92
+ }),
93
+ errors: [
94
+ {
95
+ reason: 'media_not_found',
96
+ code: JsonRpcErrorCode.NotFound,
97
+ when: 'The TMDB ID does not resolve to a movie/show (Seerr returns HTTP 500 "Unable to retrieve movie.").',
98
+ recovery: 'Call seerr_search_media to find the correct tmdbId, then retry with the exact ID and matching mediaType.',
99
+ },
100
+ ],
101
+ async handler(input, ctx) {
102
+ const seerr = getSeerrService();
103
+ if (input.mediaType === 'movie') {
104
+ const movie = await seerr.getMovie(input.tmdbId, ctx);
105
+ const media = movie.mediaInfo;
106
+ const openRequest = redactOpenRequest(media);
107
+ const availability = {
108
+ tracked: media !== undefined,
109
+ ...redactAvailability(media ?? {}),
110
+ ...(openRequest ? { openRequest } : {}),
111
+ };
112
+ ctx.log.info('Seerr movie detail fetched', {
113
+ tmdbId: input.tmdbId,
114
+ tracked: media !== undefined,
115
+ });
116
+ return {
117
+ tmdbId: movie.id,
118
+ mediaType: 'movie',
119
+ title: movie.title ?? '',
120
+ ...(yearOf(movie.releaseDate) !== undefined ? { year: yearOf(movie.releaseDate) } : {}),
121
+ ...(movie.overview?.trim() ? { overview: movie.overview.trim() } : {}),
122
+ ...(typeof movie.runtime === 'number' ? { runtimeMinutes: movie.runtime } : {}),
123
+ ...(movie.status ? { productionStatus: movie.status } : {}),
124
+ availability,
125
+ };
126
+ }
127
+ // TV
128
+ const tv = await seerr.getTv(input.tmdbId, ctx);
129
+ const media = tv.mediaInfo;
130
+ const openRequest = redactOpenRequest(media);
131
+ const availability = {
132
+ tracked: media !== undefined,
133
+ ...redactAvailability(media ?? {}),
134
+ ...(openRequest ? { openRequest } : {}),
135
+ };
136
+ let episodes;
137
+ if (typeof input.seasonNumber === 'number') {
138
+ const season = await seerr.getSeason(input.tmdbId, input.seasonNumber, ctx);
139
+ episodes = (season.episodes ?? []).map(redactEpisode);
140
+ }
141
+ ctx.log.info('Seerr TV detail fetched', {
142
+ tmdbId: input.tmdbId,
143
+ tracked: media !== undefined,
144
+ season: input.seasonNumber,
145
+ });
146
+ return {
147
+ tmdbId: tv.id,
148
+ mediaType: 'tv',
149
+ title: tv.name ?? '',
150
+ ...(yearOf(tv.firstAirDate) !== undefined ? { year: yearOf(tv.firstAirDate) } : {}),
151
+ ...(tv.overview?.trim() ? { overview: tv.overview.trim() } : {}),
152
+ ...(tv.status ? { productionStatus: tv.status } : {}),
153
+ availability,
154
+ seasons: (tv.seasons ?? []).map(redactSeason),
155
+ ...(episodes ? { episodes } : {}),
156
+ };
157
+ },
158
+ format: (result) => {
159
+ const lines = [];
160
+ const yearPart = result.year !== undefined ? ` (${result.year})` : '';
161
+ lines.push(`# ${result.title}${yearPart} — ${result.mediaType}`);
162
+ const facts = [`**TMDB ID:** ${result.tmdbId}`];
163
+ if (result.runtimeMinutes !== undefined)
164
+ facts.push(`**Runtime:** ${result.runtimeMinutes} min`);
165
+ if (result.productionStatus)
166
+ facts.push(`**Status:** ${result.productionStatus}`);
167
+ lines.push(facts.join(' | '));
168
+ const a = result.availability;
169
+ const fourK = a.status4k ? ` | 4K: ${statusText(a.status4k)}` : '';
170
+ lines.push(`**Tracked by Seerr:** ${a.tracked ? 'Yes' : 'No'} | **Availability:** ${statusText(a.status)}${fourK}`);
171
+ if (a.openRequest) {
172
+ lines.push(`**Open request:** #${a.openRequest.requestId} — status ${statusText(a.openRequest.status)} | 4K=${a.openRequest.is4k} — track with seerr_request_status.`);
173
+ }
174
+ if (result.overview) {
175
+ lines.push('');
176
+ lines.push(result.overview);
177
+ }
178
+ if (result.seasons && result.seasons.length > 0) {
179
+ lines.push('');
180
+ lines.push('## Seasons');
181
+ for (const s of result.seasons) {
182
+ lines.push(`- **S${s.seasonNumber} ${s.name}** — ${s.episodeCount} episodes${s.airDate ? ` (aired ${s.airDate})` : ''}`);
183
+ }
184
+ }
185
+ if (result.episodes && result.episodes.length > 0) {
186
+ lines.push('');
187
+ lines.push('## Episodes');
188
+ for (const e of result.episodes) {
189
+ lines.push(`- **E${e.episodeNumber} ${e.name}**${e.airDate ? ` (${e.airDate})` : ''}`);
190
+ if (e.overview)
191
+ lines.push(` ${e.overview}`);
192
+ }
193
+ }
194
+ return [{ type: 'text', text: lines.join('\n') }];
195
+ },
196
+ });
197
+ //# sourceMappingURL=get-media.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-media.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-media.tool.ts"],"names":[],"mappings":"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"}