Skip to main content
Glama

request_media

Request movies or TV shows through Overseerr with automatic confirmation for TV series up to 24 episodes. Specify seasons for TV requests and validate before submitting.

Instructions

Request media with auto-confirm for TV ≤24 eps. Single/batch with validation. Confirm: Movies auto | TV ≤24 eps auto | TV >24 eps needs confirmed:true TV needs seasons (array or "all"). "all"=no specials; [0,1,2]=with specials

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
mediaTypeNoMedia type (single)
mediaIdNoTMDB ID (single)
itemsNoBatch items
seasonsNoTV seasons. "all"=no season 0 (specials); [0,1,2]=with specials
is4kNoRequest 4K
serverIdNo
profileIdNo
rootFolderNo
validateFirstNoCheck existing
dryRunNoPreview only
confirmedNoConfirm multi-season

Implementation Reference

  • TypeScript interface defining the input arguments/schema for the 'request_media' tool, used in registration and handler.
    export interface RequestMediaArgs {
      mediaType?: 'movie' | 'tv';
      mediaId?: number;
      items?: Array<{
        mediaType: 'movie' | 'tv';
        mediaId: number;
        seasons?: number[] | 'all';
        is4k?: boolean;
      }>;
      seasons?: number[] | 'all';
      is4k?: boolean;
      serverId?: number;
      profileId?: number;
      rootFolder?: string;
      validateFirst?: boolean;
      dryRun?: boolean;
      confirmed?: boolean;
    }
  • src/index.ts:455-523 (registration)
    MCP tool registration in the server's listTools handler, defining the 'request_media' tool name, description, and inputSchema.
    name: 'request_media',
    description:
      'Request media with auto-confirm for TV ≤24 eps. Single/batch with validation.\n' +
      'Confirm: Movies auto | TV ≤24 eps auto | TV >24 eps needs confirmed:true\n' +
      'TV needs seasons (array or "all"). "all"=no specials; [0,1,2]=with specials',
    inputSchema: {
      type: 'object',
      properties: {
        mediaType: {
          type: 'string',
          enum: ['movie', 'tv'],
          description: 'Media type (single)',
        },
        mediaId: {
          type: 'number',
          description: 'TMDB ID (single)',
        },
        items: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              mediaType: { type: 'string', enum: ['movie', 'tv'] },
              mediaId: { type: 'number' },
              seasons: {
                oneOf: [
                  { type: 'array', items: { type: 'number' } },
                  { type: 'string', enum: ['all'] },
                ],
                description: 'TV seasons (REQUIRED). "all"=no season 0 (specials); [0,1,2]=with specials',
              },
              is4k: { type: 'boolean' },
            },
            required: ['mediaType', 'mediaId'],
          },
          description: 'Batch items',
        },
        seasons: {
          oneOf: [
            { type: 'array', items: { type: 'number' } },
            { type: 'string', enum: ['all'] },
          ],
          description: 'TV seasons. "all"=no season 0 (specials); [0,1,2]=with specials',
        },
        is4k: {
          type: 'boolean',
          description: 'Request 4K',
          default: false,
        },
        serverId: { type: 'number' },
        profileId: { type: 'number' },
        rootFolder: { type: 'string' },
        validateFirst: {
          type: 'boolean',
          description: 'Check existing',
          default: true,
        },
        dryRun: {
          type: 'boolean',
          description: 'Preview only',
          default: false,
        },
        confirmed: {
          type: 'boolean',
          description: 'Confirm multi-season',
          default: false,
        },
      },
    },
  • Main entry point handler for 'request_media' tool calls, dispatched from the generic CallToolRequestSchema handler. Dispatches to batch or single request logic.
    private async handleRequestMedia(args: any) {
      const requestArgs = args as RequestMediaArgs;
    
      // Batch mode
      if (requestArgs.items && requestArgs.items.length > 0) {
        return this.handleBatchRequest(requestArgs);
      }
    
      // Single mode
      if (!requestArgs.mediaType || !requestArgs.mediaId) {
        throw new McpError(
          ErrorCode.InvalidParams,
          'Must provide mediaType and mediaId (or items array for batch)'
        );
      }
    
      return this.handleSingleRequest(requestArgs);
    }
  • Core single-item request handler implementing validation (TV seasons required), season expansion ('all' -> actual seasons excluding specials), pre-request availability check, auto-confirmation for small TV requests, dry-run preview, and API request to Overseerr.
    private async handleSingleRequest(args: RequestMediaArgs) {
      const { mediaType, mediaId, seasons, is4k, validateFirst, dryRun, confirmed } = args;
    
      // Validate TV show requests have seasons specified
      if (mediaType === 'tv' && !seasons) {
        throw new McpError(
          ErrorCode.InvalidParams,
          'seasons parameter is required for TV show requests. Use seasons: [1,2,3] for specific seasons or seasons: "all" for all seasons.'
        );
      }
    
      // Expand "all" to actual season numbers (excluding season 0) early in the function
      let expandedSeasons: number[] | undefined = undefined;
      if (mediaType === 'tv' && seasons) {
        if (seasons === 'all') {
          const response = await this.axiosInstance.get<MediaDetails>(`/tv/${mediaId}`);
          const details = response.data;
          
          // Get all regular seasons (exclude season 0 - specials)
          const regularSeasons = details.seasons?.filter(s => s.seasonNumber > 0) || [];
          expandedSeasons = regularSeasons.map(s => s.seasonNumber);
          
          // If no regular seasons found, fall back to numberOfSeasons
          if (expandedSeasons.length === 0 && details.numberOfSeasons) {
            expandedSeasons = Array.from({ length: details.numberOfSeasons }, (_, i) => i + 1);
          }
        } else {
          // Already an array, use as-is
          expandedSeasons = seasons as number[];
        }
      }
    
      // Validate first if requested
      if (validateFirst) {
        const detailsCacheKey = { mediaType, mediaId };
        let details = this.cache.get<MediaDetails>('mediaDetails', detailsCacheKey);
        
        if (!details) {
          const response = await this.axiosInstance.get<MediaDetails>(
            `/${mediaType}/${mediaId}`
          );
          details = response.data;
          this.cache.set('mediaDetails', detailsCacheKey, details);
        }
    
        const mediaInfo = details.mediaInfo;
        if (mediaInfo?.requests && mediaInfo.requests.length > 0) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  success: false,
                  status: 'ALREADY_REQUESTED',
                  message: `${details.title || details.name} is already requested`,
                  existingRequests: mediaInfo.requests.map(r => ({
                    id: r.id,
                    status: this.getStatusString(r.status),
                    requestedBy: r.requestedBy.displayName || r.requestedBy.email,
                    createdAt: r.createdAt,
                  })),
                }, null, 2),
              },
            ],
          };
        }
    
        if (mediaInfo?.status === 5) { // AVAILABLE
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  success: false,
                  status: 'ALREADY_AVAILABLE',
                  message: `${details.title || details.name} is already available`,
                }, null, 2),
              },
            ],
          };
        }
      }
    
      // Multi-season confirmation check
      if (mediaType === 'tv' && !confirmed && expandedSeasons) {
        const requireConfirm = process.env.REQUIRE_MULTI_SEASON_CONFIRM !== 'false';
        
        if (requireConfirm) {
          // Get details to calculate episode count
          const response = await this.axiosInstance.get<MediaDetails>(`/tv/${mediaId}`);
          const details = response.data;
    
          const totalSeasons = details.numberOfSeasons || 0;
          const seasonsToRequest = expandedSeasons;
    
          // Calculate total episode count for requested seasons
          let totalEpisodes = 0;
          if (details.seasons) {
            seasonsToRequest.forEach((seasonNum: number) => {
              const seasonData = details.seasons?.find(s => s.seasonNumber === seasonNum);
              if (seasonData) {
                totalEpisodes += seasonData.episodeCount;
              }
            });
          }
    
          // Only require confirmation if episode count exceeds threshold (24)
          const EPISODE_THRESHOLD = 24;
          if (totalEpisodes > EPISODE_THRESHOLD) {
            // Build message including episode count for context
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify({
                    requiresConfirmation: true,
                    media: {
                      totalSeasons,
                      totalEpisodes: details.numberOfEpisodes,
                      requestingSeasons: seasonsToRequest,
                      requestingEpisodes: totalEpisodes,
                      threshold: EPISODE_THRESHOLD,
                    },
                    message: `This will request ${seasonsToRequest.length} season(s) with ${totalEpisodes} episodes of ${details.name}. Add "confirmed: true" to proceed.`,
                    confirmWith: {
                      ...args,
                      confirmed: true,
                    },
                  }, null, 2),
                },
              ],
            };
          }
        }
      }
    
      // Dry run - don't actually request
      if (dryRun) {
        const response = await this.axiosInstance.get<MediaDetails>(
          `/${mediaType}/${mediaId}`
        );
        const details = response.data;
    
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                dryRun: true,
                wouldRequest: {
                  title: details.title || details.name,
                  mediaType,
                  mediaId,
                  seasons: mediaType === 'tv' ? expandedSeasons : undefined,
                  is4k: is4k || false,
                },
                message: 'Dry run - no request was made. Remove "dryRun: true" to actually request.',
              }, null, 2),
            },
          ],
        };
      }
    
      // Actually make the request
      const requestBody: any = {
        mediaType,
        mediaId,
        is4k: is4k || false,
      };
    
      // Use expandedSeasons (array) to ensure season 0 is not included
      if (mediaType === 'tv' && expandedSeasons) {
        requestBody.seasons = expandedSeasons;
      }
    
      if (args.serverId) requestBody.serverId = args.serverId;
      if (args.profileId) requestBody.profileId = args.profileId;
      if (args.rootFolder) requestBody.rootFolder = args.rootFolder;
    
      const response = await withRetry(async () => {
        return await this.axiosInstance.post('/request', requestBody);
      });
    
      // Invalidate caches
      this.cache.invalidate('requests');
      this.cache.invalidate('mediaDetails');
    
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              success: true,
              requestId: response.data.id,
              status: this.getStatusString(response.data.status),
              message: `Successfully requested ${response.data.media.title || response.data.media.name}`,
              seasonsRequested: response.data.seasons?.map((s: any) => s.seasonNumber),
            }, null, 2),
          },
        ],
      };
    }
  • Batch request handler for multiple items, using batchWithRetry to call handleSingleRequest on each item and aggregate results.
    private async handleBatchRequest(args: RequestMediaArgs) {
      const items = args.items!;
    
      const results = await batchWithRetry(
        items,
        async (item) => {
          const singleArgs = {
            ...args,
            mediaType: item.mediaType,
            mediaId: item.mediaId,
            seasons: item.seasons,
            is4k: item.is4k,
            items: undefined,
          };
          
          const result = await this.handleSingleRequest(singleArgs);
          return JSON.parse(result.content[0].text);
        }
      );
    
      const successful = results.filter(r => r.success && r.result?.success);
      const failed = results.filter(r => !r.success || !r.result?.success);
    
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              summary: {
                total: items.length,
                successful: successful.length,
                failed: failed.length,
              },
              results: successful.map(r => r.result),
              errors: failed.map(r => ({
                item: r.item,
                error: r.error?.message || r.result?.message || 'Unknown error',
              })),
            }, null, 2),
          },
        ],
      };
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jhomen368/overseerr-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server