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