@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,111 @@
1
+ /**
2
+ * @fileoverview seerr_request_status — track one request through its lifecycle.
3
+ * Wraps `GET /request/{id}`. Returns decoded request + media status, requester
4
+ * (PII-redacted), a routing summary (names/IDs only — no filesystem paths), and a
5
+ * status-tuned recovery hint. A missing request id surfaces as request_not_found
6
+ * (Seerr's raw HTTP 404 is classified in the service layer). `profileName` is null
7
+ * at this endpoint, so routing.profileName is optional; `title` is not on the
8
+ * request object, so it is left unpopulated.
9
+ * @module mcp-server/tools/definitions/request-status.tool
10
+ */
11
+ import { tool, z } from '@cyanheads/mcp-ts-core';
12
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
13
+ import { projectRequestDetail } from '../../../services/seerr/normalizers.js';
14
+ import { getSeerrService } from '../../../services/seerr/seerr-service.js';
15
+ import { StatusRef, statusText } from '../../../services/seerr/status.js';
16
+ export const requestStatusTool = tool('seerr_request_status', {
17
+ title: 'seerr-mcp-server: request status',
18
+ description: 'Fetch one media request by ID and return its decoded request status, decoded media availability (including 4K), requester, a routing summary, and a next-step hint tuned to the current state. Get the requestId from seerr_request_media (created.requestId) or seerr_list_requests.',
19
+ annotations: { readOnlyHint: true, openWorldHint: true },
20
+ input: z.object({
21
+ requestId: z
22
+ .number()
23
+ .int()
24
+ .positive()
25
+ .describe('Request ID from seerr_request_media (created.requestId) or seerr_list_requests (requests[].requestId).'),
26
+ }),
27
+ output: z.object({
28
+ requestId: z.number().describe('The request ID.'),
29
+ mediaType: z.enum(['movie', 'tv']).describe('Movie or TV.'),
30
+ title: z.string().optional().describe('Title of the requested media when joinable.'),
31
+ tmdbId: z.number().optional().describe('TMDB ID of the media.'),
32
+ requestStatus: StatusRef.describe('Decoded request status {raw,label}.'),
33
+ mediaStatus: StatusRef.optional().describe('Decoded media availability {raw,label}.'),
34
+ mediaStatus4k: StatusRef.optional().describe('Decoded 4K availability {raw,label} when applicable.'),
35
+ is4k: z.boolean().describe('Whether this is a 4K request.'),
36
+ seasons: z.array(z.number()).optional().describe('Requested season numbers (TV).'),
37
+ requestedBy: z
38
+ .object({
39
+ id: z.number().describe('Requester user ID.'),
40
+ displayName: z.string().describe('Requester display name (no email/tokens).'),
41
+ })
42
+ .describe('Who requested it — id + display name only.'),
43
+ routing: z
44
+ .object({
45
+ serverId: z.number().optional().describe('Radarr/Sonarr server ID handling the request.'),
46
+ profileName: z
47
+ .string()
48
+ .optional()
49
+ .describe('Quality profile name; null when not set on this request.'),
50
+ is4k: z.boolean().describe('Whether routed to the 4K service.'),
51
+ })
52
+ .describe('Routing summary — names/IDs only, no filesystem paths.'),
53
+ createdAt: z.string().describe('ISO creation timestamp.'),
54
+ updatedAt: z.string().describe('ISO last-updated timestamp.'),
55
+ stateGuidance: z
56
+ .string()
57
+ .optional()
58
+ .describe('Recovery/next-step hint tuned to the current status, e.g. pending → "awaiting approval".'),
59
+ }),
60
+ errors: [
61
+ {
62
+ reason: 'request_not_found',
63
+ code: JsonRpcErrorCode.NotFound,
64
+ when: 'No request exists with the given ID (Seerr returns HTTP 404 "Request not found.").',
65
+ recovery: 'List requests with seerr_list_requests to find a valid requestId, then retry.',
66
+ },
67
+ ],
68
+ async handler(input, ctx) {
69
+ const seerr = getSeerrService();
70
+ const raw = await seerr.getRequest(input.requestId, ctx);
71
+ const detail = projectRequestDetail(raw);
72
+ ctx.log.info('Seerr request status fetched', {
73
+ requestId: input.requestId,
74
+ status: detail.requestStatus.label,
75
+ });
76
+ return detail;
77
+ },
78
+ format: (result) => {
79
+ const lines = [];
80
+ lines.push(`# Request #${result.requestId} — ${result.title ?? 'Untitled'} (${result.mediaType}, 4K=${result.is4k})`);
81
+ lines.push(`**TMDB ID:** ${result.tmdbId ?? 'n/a'}`);
82
+ const status = [`**Request:** ${statusText(result.requestStatus)}`];
83
+ if (result.mediaStatus)
84
+ status.push(`**Media:** ${statusText(result.mediaStatus)}`);
85
+ if (result.mediaStatus4k)
86
+ status.push(`**Media 4K:** ${statusText(result.mediaStatus4k)}`);
87
+ lines.push(status.join(' | '));
88
+ lines.push(`**Requested by:** ${result.requestedBy.displayName} (id ${result.requestedBy.id})`);
89
+ const routing = [`**Routed 4K=${result.routing.is4k}**`];
90
+ if (result.routing.serverId !== undefined)
91
+ routing.push(`**Server ID:** ${result.routing.serverId}`);
92
+ if (result.routing.profileName)
93
+ routing.push(`**Profile:** ${result.routing.profileName}`);
94
+ lines.push(routing.join(' | '));
95
+ if (result.seasons && result.seasons.length > 0)
96
+ lines.push(`**Seasons:** ${result.seasons.join(', ')}`);
97
+ const timestamps = [];
98
+ if (result.createdAt)
99
+ timestamps.push(`**Created:** ${result.createdAt}`);
100
+ if (result.updatedAt)
101
+ timestamps.push(`**Updated:** ${result.updatedAt}`);
102
+ if (timestamps.length > 0)
103
+ lines.push(timestamps.join(' | '));
104
+ if (result.stateGuidance) {
105
+ lines.push('');
106
+ lines.push(`> ${result.stateGuidance}`);
107
+ }
108
+ return [{ type: 'text', text: lines.join('\n') }];
109
+ },
110
+ });
111
+ //# sourceMappingURL=request-status.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-status.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/request-status.tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @fileoverview seerr_search_media — title disambiguation entry point. Wraps
3
+ * `GET /search`, filters to movie/TV (drops `person`), normalizes each hit, and
4
+ * decodes `mediaInfo` availability when Seerr already tracks the title. The
5
+ * required first step before any request. Search-result `mediaInfo` carries only
6
+ * `status`/`status4k` (no open-request data — that lives on the detail endpoints).
7
+ * @module mcp-server/tools/definitions/search-media.tool
8
+ */
9
+ import { z } from '@cyanheads/mcp-ts-core';
10
+ export declare const searchMediaTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
11
+ query: z.ZodString;
12
+ mediaType: z.ZodDefault<z.ZodEnum<{
13
+ movie: "movie";
14
+ tv: "tv";
15
+ all: "all";
16
+ }>>;
17
+ page: z.ZodDefault<z.ZodNumber>;
18
+ language: z.ZodOptional<z.ZodString>;
19
+ limit: z.ZodDefault<z.ZodNumber>;
20
+ }, z.core.$strip>, z.ZodObject<{
21
+ results: z.ZodArray<z.ZodObject<{
22
+ tmdbId: z.ZodNumber;
23
+ mediaType: z.ZodEnum<{
24
+ movie: "movie";
25
+ tv: "tv";
26
+ }>;
27
+ title: z.ZodString;
28
+ year: z.ZodOptional<z.ZodNumber>;
29
+ overview: z.ZodOptional<z.ZodString>;
30
+ voteAverage: z.ZodOptional<z.ZodNumber>;
31
+ tracked: z.ZodBoolean;
32
+ availability: z.ZodOptional<z.ZodObject<{
33
+ status: z.ZodObject<{
34
+ raw: z.ZodNumber;
35
+ label: z.ZodString;
36
+ }, z.core.$strip>;
37
+ status4k: z.ZodOptional<z.ZodObject<{
38
+ raw: z.ZodNumber;
39
+ label: z.ZodString;
40
+ }, z.core.$strip>>;
41
+ }, z.core.$strip>>;
42
+ }, z.core.$strip>>;
43
+ }, z.core.$strip>, undefined, {
44
+ readonly effectiveQuery: z.ZodString;
45
+ readonly totalCount: z.ZodNumber;
46
+ readonly truncated: z.ZodOptional<z.ZodBoolean>;
47
+ readonly shown: z.ZodOptional<z.ZodNumber>;
48
+ readonly cap: z.ZodOptional<z.ZodNumber>;
49
+ readonly notice: z.ZodOptional<z.ZodString>;
50
+ }>;
51
+ //# sourceMappingURL=search-media.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-media.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-media.tool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAajD,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoL1B,CAAC"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * @fileoverview seerr_search_media — title disambiguation entry point. Wraps
3
+ * `GET /search`, filters to movie/TV (drops `person`), normalizes each hit, and
4
+ * decodes `mediaInfo` availability when Seerr already tracks the title. The
5
+ * required first step before any request. Search-result `mediaInfo` carries only
6
+ * `status`/`status4k` (no open-request data — that lives on the detail endpoints).
7
+ * @module mcp-server/tools/definitions/search-media.tool
8
+ */
9
+ import { tool, z } from '@cyanheads/mcp-ts-core';
10
+ import { redactAvailability } from '../../../services/seerr/normalizers.js';
11
+ import { getSeerrService } from '../../../services/seerr/seerr-service.js';
12
+ import { StatusRef, statusText } from '../../../services/seerr/status.js';
13
+ /** Parse a 4-digit year from an ISO-ish date string; returns undefined when unparseable. */
14
+ function yearOf(date) {
15
+ if (!date)
16
+ return;
17
+ const year = Number.parseInt(date.slice(0, 4), 10);
18
+ return Number.isFinite(year) ? year : undefined;
19
+ }
20
+ export const searchMediaTool = tool('seerr_search_media', {
21
+ title: 'seerr-mcp-server: search media',
22
+ description: 'Search movies and TV shows by title and return ranked matches with TMDB ID, year, overview, and decoded availability status when Seerr already tracks the title. People are always excluded. This is the required first step before requesting media — use the returned tmdbId with seerr_get_media or seerr_request_media.',
23
+ annotations: { readOnlyHint: true, openWorldHint: true },
24
+ input: z.object({
25
+ query: z
26
+ .string()
27
+ .min(1)
28
+ .describe('Title to search for, e.g. "Mulan" or "Severance". Natural-language titles are fine; Seerr matches against TMDB.'),
29
+ mediaType: z
30
+ .enum(['movie', 'tv', 'all'])
31
+ .default('all')
32
+ .describe('Restrict results to movies, TV shows, or both. People are always excluded — this server requests media, not actors.'),
33
+ page: z
34
+ .number()
35
+ .int()
36
+ .min(1)
37
+ .max(1000)
38
+ .default(1)
39
+ .describe('1-based result page. Seerr returns ~20 results per page; use with a prior call to page through.'),
40
+ language: z
41
+ .string()
42
+ .regex(/^[a-z]{2}(-[A-Z]{2})?$/)
43
+ .optional()
44
+ .describe('ISO 639-1 language for titles/overviews, e.g. "en" or "pt-BR". Defaults to the Seerr instance locale when omitted.'),
45
+ limit: z
46
+ .number()
47
+ .int()
48
+ .min(1)
49
+ .max(20)
50
+ .default(10)
51
+ .describe('Max normalized results to return from this page (caps output size; does not change which page is fetched).'),
52
+ }),
53
+ output: z.object({
54
+ results: z
55
+ .array(z
56
+ .object({
57
+ tmdbId: z
58
+ .number()
59
+ .describe('TMDB ID — pass to seerr_get_media or seerr_request_media as tmdbId.'),
60
+ mediaType: z.enum(['movie', 'tv']).describe('Whether this is a movie or TV show.'),
61
+ title: z.string().describe('Display title (movie title or show name).'),
62
+ year: z
63
+ .number()
64
+ .optional()
65
+ .describe('Release year (movies) or first-air year (TV); omitted when the date is unknown.'),
66
+ overview: z.string().optional().describe('Short synopsis; omitted when TMDB has none.'),
67
+ voteAverage: z
68
+ .number()
69
+ .optional()
70
+ .describe('TMDB vote average (0–10) for relevance/quality signal.'),
71
+ tracked: z
72
+ .boolean()
73
+ .describe('True when Seerr already tracks this title (mediaInfo present) — availability data follows.'),
74
+ availability: z
75
+ .object({
76
+ status: StatusRef.describe('Decoded media availability {raw,label}.'),
77
+ status4k: StatusRef.optional().describe('Decoded 4K availability {raw,label} when the instance has 4K enabled.'),
78
+ })
79
+ .optional()
80
+ .describe('Current Seerr availability; present only when tracked is true.'),
81
+ })
82
+ .describe('A single ranked movie/TV match.'))
83
+ .describe('Ranked movie/TV matches. Empty array when nothing matched.'),
84
+ }),
85
+ enrichment: {
86
+ effectiveQuery: z.string().describe('The query as the server parsed it.'),
87
+ totalCount: z
88
+ .number()
89
+ .describe('Total results Seerr reported for the query before this page limit.'),
90
+ truncated: z
91
+ .boolean()
92
+ .optional()
93
+ .describe('True when this page yielded more results than the limit returned.'),
94
+ shown: z.number().optional().describe('Number of results returned after the limit.'),
95
+ cap: z.number().optional().describe('The limit that was applied.'),
96
+ notice: z.string().optional().describe('Guidance when nothing matched.'),
97
+ },
98
+ async handler(input, ctx) {
99
+ const seerr = getSeerrService();
100
+ const response = await seerr.search({
101
+ query: input.query,
102
+ page: input.page,
103
+ ...(input.language ? { language: input.language } : {}),
104
+ }, ctx);
105
+ const wanted = (input.mediaType === 'all' ? ['movie', 'tv'] : [input.mediaType]);
106
+ const matches = (response.results ?? [])
107
+ .filter((r) => r.mediaType === 'movie' || r.mediaType === 'tv')
108
+ .filter((r) => wanted.includes(r.mediaType));
109
+ const limited = matches.slice(0, input.limit);
110
+ const results = limited.map((r) => {
111
+ const isMovie = r.mediaType === 'movie';
112
+ const tracked = r.mediaInfo !== undefined;
113
+ return {
114
+ tmdbId: r.id,
115
+ mediaType: r.mediaType,
116
+ title: (isMovie ? r.title : r.name) ?? '',
117
+ ...(yearOf(isMovie ? r.releaseDate : r.firstAirDate) !== undefined
118
+ ? { year: yearOf(isMovie ? r.releaseDate : r.firstAirDate) }
119
+ : {}),
120
+ ...(r.overview?.trim() ? { overview: r.overview.trim() } : {}),
121
+ ...(typeof r.voteAverage === 'number' ? { voteAverage: r.voteAverage } : {}),
122
+ tracked,
123
+ ...(tracked && r.mediaInfo ? { availability: redactAvailability(r.mediaInfo) } : {}),
124
+ };
125
+ });
126
+ ctx.enrich.echo(input.query);
127
+ ctx.enrich.total(response.totalResults ?? matches.length);
128
+ if (matches.length > input.limit) {
129
+ ctx.enrich.truncated({ shown: results.length, cap: input.limit });
130
+ }
131
+ if (results.length === 0) {
132
+ ctx.enrich.notice(`No movie/TV match for "${input.query}". Try a different spelling or broaden mediaType to "all".`);
133
+ }
134
+ ctx.log.info('Seerr search completed', {
135
+ query: input.query,
136
+ matched: matches.length,
137
+ returned: results.length,
138
+ });
139
+ return { results };
140
+ },
141
+ format: (result) => {
142
+ if (result.results.length === 0) {
143
+ return [{ type: 'text', text: 'No matching movies or TV shows.' }];
144
+ }
145
+ const lines = [];
146
+ for (const r of result.results) {
147
+ const yearPart = r.year !== undefined ? ` (${r.year})` : '';
148
+ lines.push(`## ${r.title}${yearPart} — ${r.mediaType}`);
149
+ const facts = [`**TMDB ID:** ${r.tmdbId}`];
150
+ if (r.voteAverage !== undefined)
151
+ facts.push(`**Rating:** ${r.voteAverage.toFixed(1)}/10`);
152
+ facts.push(`**Tracked by Seerr:** ${r.tracked ? 'Yes' : 'No'}`);
153
+ lines.push(facts.join(' | '));
154
+ if (r.availability) {
155
+ const fourK = r.availability.status4k
156
+ ? ` | 4K: ${statusText(r.availability.status4k)}`
157
+ : '';
158
+ lines.push(`**Availability:** ${statusText(r.availability.status)}${fourK}`);
159
+ }
160
+ if (r.overview)
161
+ lines.push(r.overview);
162
+ lines.push('');
163
+ }
164
+ return [{ type: 'text', text: lines.join('\n').trimEnd() }];
165
+ },
166
+ });
167
+ //# sourceMappingURL=search-media.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-media.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-media.tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @fileoverview seerr_service_options — let an agent reason about request
3
+ * capability and routing without a separate status tool. Summarizes configured
4
+ * Radarr/Sonarr services + default quality profiles, folds in compact Seerr
5
+ * version and public feature flags (4K, partial requests, specials). Filesystem
6
+ * paths (root folders, `activeDirectory`/`activeAnimeDirectory`) are redacted
7
+ * unless `includePaths: true`; `vapidPublic` and `plexClientIdentifier` from
8
+ * `/settings/public` are never surfaced. Legs fan out with `Promise.allSettled`
9
+ * so one failure degrades to a notice rather than failing the call.
10
+ * @module mcp-server/tools/definitions/service-options.tool
11
+ */
12
+ import { z } from '@cyanheads/mcp-ts-core';
13
+ export declare const serviceOptionsTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
14
+ service: z.ZodDefault<z.ZodEnum<{
15
+ all: "all";
16
+ radarr: "radarr";
17
+ sonarr: "sonarr";
18
+ }>>;
19
+ includePaths: z.ZodDefault<z.ZodBoolean>;
20
+ }, z.core.$strip>, z.ZodObject<{
21
+ instance: z.ZodObject<{
22
+ version: z.ZodString;
23
+ mediaServer: z.ZodString;
24
+ movie4kEnabled: z.ZodBoolean;
25
+ series4kEnabled: z.ZodBoolean;
26
+ partialRequestsEnabled: z.ZodBoolean;
27
+ specialEpisodesEnabled: z.ZodBoolean;
28
+ }, z.core.$strip>;
29
+ services: z.ZodArray<z.ZodObject<{
30
+ kind: z.ZodEnum<{
31
+ radarr: "radarr";
32
+ sonarr: "sonarr";
33
+ }>;
34
+ serverId: z.ZodNumber;
35
+ name: z.ZodString;
36
+ isDefault: z.ZodBoolean;
37
+ is4k: z.ZodBoolean;
38
+ activeProfileId: z.ZodNumber;
39
+ profiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
40
+ id: z.ZodNumber;
41
+ name: z.ZodString;
42
+ }, z.core.$strip>>>;
43
+ rootFolders: z.ZodOptional<z.ZodArray<z.ZodObject<{
44
+ path: z.ZodString;
45
+ freeSpace: z.ZodOptional<z.ZodNumber>;
46
+ }, z.core.$strip>>>;
47
+ }, z.core.$strip>>;
48
+ }, z.core.$strip>, undefined, {
49
+ readonly notice: z.ZodOptional<z.ZodString>;
50
+ }>;
51
+ //# sourceMappingURL=service-options.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-options.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/service-options.tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAoEjD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2K7B,CAAC"}
@@ -0,0 +1,195 @@
1
+ /**
2
+ * @fileoverview seerr_service_options — let an agent reason about request
3
+ * capability and routing without a separate status tool. Summarizes configured
4
+ * Radarr/Sonarr services + default quality profiles, folds in compact Seerr
5
+ * version and public feature flags (4K, partial requests, specials). Filesystem
6
+ * paths (root folders, `activeDirectory`/`activeAnimeDirectory`) are redacted
7
+ * unless `includePaths: true`; `vapidPublic` and `plexClientIdentifier` from
8
+ * `/settings/public` are never surfaced. Legs fan out with `Promise.allSettled`
9
+ * so one failure degrades to a notice rather than failing the call.
10
+ * @module mcp-server/tools/definitions/service-options.tool
11
+ */
12
+ import { tool, z } from '@cyanheads/mcp-ts-core';
13
+ import { redactProfile, redactRootFolder } from '../../../services/seerr/normalizers.js';
14
+ import { getSeerrService } from '../../../services/seerr/seerr-service.js';
15
+ import { decodeMediaServer } from '../../../services/seerr/status.js';
16
+ /**
17
+ * Build one service summary: always fetches the detail endpoint for profile names;
18
+ * includes root folders only when `includePaths`. A failed detail leg is recorded
19
+ * as a warning and the profiles/folders are omitted (degraded, but honest).
20
+ */
21
+ async function buildService(kind, entry, includePaths, seerr, ctx, warnings) {
22
+ const base = {
23
+ kind,
24
+ serverId: entry.id,
25
+ name: entry.name ?? `${kind} ${entry.id}`,
26
+ isDefault: entry.isDefault === true,
27
+ is4k: entry.is4k === true,
28
+ activeProfileId: entry.activeProfileId ?? 0,
29
+ };
30
+ let detail;
31
+ try {
32
+ detail =
33
+ kind === 'radarr'
34
+ ? await seerr.getRadarrDetail(entry.id, ctx)
35
+ : await seerr.getSonarrDetail(entry.id, ctx);
36
+ }
37
+ catch {
38
+ warnings.push(`${kind === 'radarr' ? 'Radarr' : 'Sonarr'} details unavailable for server ${entry.id}; profile list omitted.`);
39
+ return base;
40
+ }
41
+ const profiles = (detail.profiles ?? []).map(redactProfile);
42
+ const rootFolders = includePaths
43
+ ? (detail.rootFolders ?? [])
44
+ .map((f) => redactRootFolder(f, includePaths))
45
+ .filter((f) => f !== undefined)
46
+ : undefined;
47
+ return {
48
+ ...base,
49
+ ...(profiles.length > 0 ? { profiles } : {}),
50
+ ...(rootFolders ? { rootFolders } : {}),
51
+ };
52
+ }
53
+ export const serviceOptionsTool = tool('seerr_service_options', {
54
+ title: 'seerr-mcp-server: service options',
55
+ description: 'Summarize the configured Radarr (movie) and Sonarr (TV) services, their default quality profiles, and the Seerr instance capability flags (4K, partial requests, specials, media server) so an agent can reason about what kinds of requests will be accepted and how routing works. Filesystem paths are redacted unless includePaths is set.',
56
+ annotations: { readOnlyHint: true, openWorldHint: true },
57
+ input: z.object({
58
+ service: z
59
+ .enum(['radarr', 'sonarr', 'all'])
60
+ .default('all')
61
+ .describe('Which service(s) to summarize. "all" covers both movie (Radarr) and TV (Sonarr) routing.'),
62
+ includePaths: z
63
+ .boolean()
64
+ .default(false)
65
+ .describe('Include filesystem root-folder paths and free space. Default false redacts paths (operator-private). Set true only when you explicitly need routing paths.'),
66
+ }),
67
+ output: z.object({
68
+ instance: z
69
+ .object({
70
+ version: z.string().describe('Seerr version, e.g. "3.3.0".'),
71
+ mediaServer: z.string().describe('Decoded media server: "plex" | "jellyfin" | "emby".'),
72
+ movie4kEnabled: z.boolean().describe('Whether 4K movie requests are allowed.'),
73
+ series4kEnabled: z.boolean().describe('Whether 4K TV requests are allowed.'),
74
+ partialRequestsEnabled: z.boolean().describe('Whether per-season TV requests are allowed.'),
75
+ specialEpisodesEnabled: z
76
+ .boolean()
77
+ .describe('Whether season 0 (Specials) can be requested.'),
78
+ })
79
+ .describe('Instance capability summary — what kinds of requests will be accepted.'),
80
+ services: z
81
+ .array(z
82
+ .object({
83
+ kind: z
84
+ .enum(['radarr', 'sonarr'])
85
+ .describe('Service type — Radarr (movies) or Sonarr (TV).'),
86
+ serverId: z
87
+ .number()
88
+ .describe('Server ID — pass to seerr_request_media serverId to override routing.'),
89
+ name: z.string().describe('Service display name.'),
90
+ isDefault: z
91
+ .boolean()
92
+ .describe('Whether this is the default server for its media type.'),
93
+ is4k: z.boolean().describe('Whether this server handles 4K.'),
94
+ activeProfileId: z
95
+ .number()
96
+ .describe('Default quality profile ID — pass as profileId to override.'),
97
+ profiles: z
98
+ .array(z
99
+ .object({
100
+ id: z.number().describe('Quality profile ID for profileId overrides.'),
101
+ name: z.string().describe('Quality profile name, e.g. "HD-1080p", "Ultra-HD".'),
102
+ })
103
+ .describe('A configured quality profile.'))
104
+ .optional()
105
+ .describe('Available quality profiles (from the detail endpoint).'),
106
+ rootFolders: z
107
+ .array(z
108
+ .object({
109
+ path: z
110
+ .string()
111
+ .describe('Root folder path — only present when includePaths is true.'),
112
+ freeSpace: z
113
+ .number()
114
+ .optional()
115
+ .describe('Free space in bytes (raw API field name: freeSpace) — only when includePaths is true.'),
116
+ })
117
+ .describe('A configured root folder.'))
118
+ .optional()
119
+ .describe('Root folders — omitted unless includePaths is true (paths are operator-private).'),
120
+ })
121
+ .describe('A configured Radarr or Sonarr service with its routing defaults.'))
122
+ .describe('Configured Radarr/Sonarr services with routing defaults.'),
123
+ }),
124
+ enrichment: {
125
+ notice: z
126
+ .string()
127
+ .optional()
128
+ .describe('Disclosure when a service leg failed and its data was omitted.'),
129
+ },
130
+ async handler(input, ctx) {
131
+ const seerr = getSeerrService();
132
+ const wantRadarr = input.service === 'radarr' || input.service === 'all';
133
+ const wantSonarr = input.service === 'sonarr' || input.service === 'all';
134
+ const warnings = [];
135
+ const [statusRes, settingsRes, radarrListRes, sonarrListRes] = await Promise.allSettled([
136
+ seerr.getStatus(ctx),
137
+ seerr.getPublicSettings(ctx),
138
+ wantRadarr ? seerr.getRadarrServices(ctx) : Promise.resolve([]),
139
+ wantSonarr ? seerr.getSonarrServices(ctx) : Promise.resolve([]),
140
+ ]);
141
+ const settings = settingsRes.status === 'fulfilled' ? settingsRes.value : {};
142
+ if (settingsRes.status === 'rejected')
143
+ warnings.push('Instance settings unavailable; capability flags may be incomplete.');
144
+ if (statusRes.status === 'rejected')
145
+ warnings.push('Seerr version unavailable.');
146
+ const instance = {
147
+ version: statusRes.status === 'fulfilled' ? (statusRes.value.version ?? 'unknown') : 'unknown',
148
+ mediaServer: decodeMediaServer(settings.mediaServerType ?? 0),
149
+ movie4kEnabled: settings.movie4kEnabled === true,
150
+ series4kEnabled: settings.series4kEnabled === true,
151
+ partialRequestsEnabled: settings.partialRequestsEnabled === true,
152
+ specialEpisodesEnabled: settings.enableSpecialEpisodes === true,
153
+ };
154
+ const radarrList = radarrListRes.status === 'fulfilled' ? radarrListRes.value : [];
155
+ if (wantRadarr && radarrListRes.status === 'rejected')
156
+ warnings.push('Radarr service list unavailable.');
157
+ const sonarrList = sonarrListRes.status === 'fulfilled' ? sonarrListRes.value : [];
158
+ if (wantSonarr && sonarrListRes.status === 'rejected')
159
+ warnings.push('Sonarr service list unavailable.');
160
+ const services = await Promise.all([
161
+ ...radarrList.map((entry) => buildService('radarr', entry, input.includePaths, seerr, ctx, warnings)),
162
+ ...sonarrList.map((entry) => buildService('sonarr', entry, input.includePaths, seerr, ctx, warnings)),
163
+ ]);
164
+ if (warnings.length > 0)
165
+ ctx.enrich.notice(warnings.join(' '));
166
+ ctx.log.info('Seerr service options assembled', {
167
+ service: input.service,
168
+ serviceCount: services.length,
169
+ warnings: warnings.length,
170
+ });
171
+ return { instance, services };
172
+ },
173
+ format: (result) => {
174
+ const lines = [];
175
+ const i = result.instance;
176
+ lines.push(`# Seerr ${i.version} — ${i.mediaServer}`);
177
+ lines.push(`**4K movies:** ${i.movie4kEnabled} | **4K series:** ${i.series4kEnabled} | **Partial requests:** ${i.partialRequestsEnabled} | **Specials:** ${i.specialEpisodesEnabled}`);
178
+ for (const s of result.services) {
179
+ lines.push('');
180
+ lines.push(`## ${s.name} — ${s.kind}${s.isDefault ? ' [default]' : ''}`);
181
+ lines.push(`**Server ID:** ${s.serverId} | **4K:** ${s.is4k ? 'Yes' : 'No'} | **Default profile ID:** ${s.activeProfileId}`);
182
+ if (s.profiles && s.profiles.length > 0) {
183
+ lines.push(`**Profiles:** ${s.profiles.map((p) => `${p.name} (#${p.id})`).join(', ')}`);
184
+ }
185
+ if (s.rootFolders && s.rootFolders.length > 0) {
186
+ for (const f of s.rootFolders) {
187
+ const free = f.freeSpace !== undefined ? ` — freeSpace ${f.freeSpace} bytes` : '';
188
+ lines.push(`**Root folder:** ${f.path}${free}`);
189
+ }
190
+ }
191
+ }
192
+ return [{ type: 'text', text: lines.join('\n') }];
193
+ },
194
+ });
195
+ //# sourceMappingURL=service-options.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-options.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/service-options.tool.ts"],"names":[],"mappings":"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"}