Skip to main content
Glama

readarr_review_setup

Analyze Readarr book management configuration to identify issues and suggest improvements for quality profiles, download clients, naming, storage, and indexers.

Instructions

Get comprehensive configuration review for Readarr (Books). Returns all settings for analysis: quality profiles, download clients, naming, storage, indexers, health warnings, and more. Use this to analyze the setup and suggest improvements.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • src/index.ts:163-170 (registration)
    Registration and schema definition for the 'readarr_review_setup' tool (via dynamic `${serviceName}_review_setup` where serviceName='readarr'). Defines no-input schema and description.
    name: `${serviceName}_review_setup`, description: `Get comprehensive configuration review for ${displayName}. Returns all settings for analysis: quality profiles, download clients, naming, storage, indexers, health warnings, and more. Use this to analyze the setup and suggest improvements.`, inputSchema: { type: "object" as const, properties: {}, required: [], }, }
  • src/index.ts:178-178 (registration)
    Conditional invocation of addConfigTools for 'readarr', which registers the 'readarr_review_setup' tool if Readarr client is configured.
    if (clients.readarr) addConfigTools('readarr', 'Readarr (Books)');
  • Primary handler logic for 'readarr_review_setup'. Dispatches to ReadarrClient methods to gather config data (status, health, profiles, etc.), optionally metadataProfiles, compiles structured review JSON.
    case "sonarr_review_setup": case "radarr_review_setup": case "lidarr_review_setup": case "readarr_review_setup": { const serviceName = name.split('_')[0] as keyof typeof clients; const client = clients[serviceName]; if (!client) throw new Error(`${serviceName} not configured`); // Gather all configuration data const [status, health, qualityProfiles, qualityDefinitions, downloadClients, naming, mediaManagement, rootFolders, tags, indexers] = await Promise.all([ client.getStatus(), client.getHealth(), client.getQualityProfiles(), client.getQualityDefinitions(), client.getDownloadClients(), client.getNamingConfig(), client.getMediaManagement(), client.getRootFoldersDetailed(), client.getTags(), client.getIndexers(), ]); // For Lidarr/Readarr, also get metadata profiles let metadataProfiles = null; if (serviceName === 'lidarr' && clients.lidarr) { metadataProfiles = await clients.lidarr.getMetadataProfiles(); } else if (serviceName === 'readarr' && clients.readarr) { metadataProfiles = await clients.readarr.getMetadataProfiles(); } const review = { service: serviceName, version: status.version, appName: status.appName, platform: { os: status.osName, isDocker: status.isDocker, }, health: { issueCount: health.length, issues: health, }, storage: { rootFolders: rootFolders.map(f => ({ path: f.path, accessible: f.accessible, freeSpace: formatBytes(f.freeSpace), freeSpaceBytes: f.freeSpace, unmappedFolderCount: f.unmappedFolders?.length || 0, })), }, qualityProfiles: qualityProfiles.map(p => ({ id: p.id, name: p.name, upgradeAllowed: p.upgradeAllowed, cutoff: p.cutoff, allowedQualities: p.items .filter(i => i.allowed) .map(i => i.quality?.name || i.name || (i.items?.map(q => q.quality.name).join(', '))) .filter(Boolean), customFormatsWithScores: p.formatItems?.filter(f => f.score !== 0).length || 0, minFormatScore: p.minFormatScore, })), qualityDefinitions: qualityDefinitions.map(d => ({ quality: d.quality.name, minSize: d.minSize + ' MB/min', maxSize: d.maxSize === 0 ? 'unlimited' : d.maxSize + ' MB/min', preferredSize: d.preferredSize + ' MB/min', })), downloadClients: downloadClients.map(c => ({ name: c.name, type: c.implementationName, protocol: c.protocol, enabled: c.enable, priority: c.priority, })), indexers: indexers.map(i => ({ name: i.name, protocol: i.protocol, enableRss: i.enableRss, enableAutomaticSearch: i.enableAutomaticSearch, enableInteractiveSearch: i.enableInteractiveSearch, priority: i.priority, })), naming: naming, mediaManagement: { recycleBin: mediaManagement.recycleBin || 'not set', recycleBinCleanupDays: mediaManagement.recycleBinCleanupDays, downloadPropersAndRepacks: mediaManagement.downloadPropersAndRepacks, deleteEmptyFolders: mediaManagement.deleteEmptyFolders, copyUsingHardlinks: mediaManagement.copyUsingHardlinks, importExtraFiles: mediaManagement.importExtraFiles, extraFileExtensions: mediaManagement.extraFileExtensions, }, tags: tags.map(t => t.label), ...(metadataProfiles && { metadataProfiles }), }; return { content: [{ type: "text", text: JSON.stringify(review, null, 2), }], }; }
  • Instantiation of ReadarrClient used by the review_setup handler.
    case 'readarr': clients.readarr = new ReadarrClient(config); break;
  • ReadarrClient class definition, extends ArrClient. Provides service-specific methods like getMetadataProfiles used in review_setup, and inherits common config-fetching methods.
    export class ReadarrClient extends ArrClient { constructor(config: ArrConfig) { super('readarr', config); this.apiVersion = 'v1'; } /** * Get all authors */ async getAuthors(): Promise<Author[]> { return this['request']<Author[]>('/author'); } /** * Get a specific author */ async getAuthorById(id: number): Promise<Author> { return this['request']<Author>(`/author/${id}`); } /** * Search for authors */ async searchAuthors(term: string): Promise<SearchResult[]> { return this['request']<SearchResult[]>(`/author/lookup?term=${encodeURIComponent(term)}`); } /** * Add an author */ async addAuthor(author: Partial<Author> & { foreignAuthorId: string; rootFolderPath: string; qualityProfileId: number; metadataProfileId: number }): Promise<Author> { return this['request']<Author>('/author', { method: 'POST', body: JSON.stringify({ ...author, monitored: author.monitored ?? true, addOptions: { searchForMissingBooks: true, }, }), }); } /** * Get books for an author */ async getBooks(authorId?: number): Promise<Book[]> { const url = authorId ? `/book?authorId=${authorId}` : '/book'; return this['request']<Book[]>(url); } /** * Get a specific book */ async getBookById(id: number): Promise<Book> { return this['request']<Book>(`/book/${id}`); } /** * Search for missing books for an author */ async searchMissingBooks(authorId: number): Promise<{ id: number }> { return this['request']<{ id: number }>('/command', { method: 'POST', body: JSON.stringify({ name: 'AuthorSearch', authorId, }), }); } /** * Search for a specific book */ async searchBook(bookIds: number[]): Promise<{ id: number }> { return this['request']<{ id: number }>('/command', { method: 'POST', body: JSON.stringify({ name: 'BookSearch', bookIds, }), }); } /** * Get calendar (upcoming book releases) */ async getCalendar(start?: string, end?: string): Promise<Book[]> { const params = new URLSearchParams(); if (start) params.append('start', start); if (end) params.append('end', end); const query = params.toString() ? `?${params.toString()}` : ''; return this['request']<Book[]>(`/calendar${query}`); } /** * Get metadata profiles */ async getMetadataProfiles(): Promise<MetadataProfile[]> { return this['request']<MetadataProfile[]>('/metadataprofile'); } }

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/aplaceforallmystuff/mcp-arr'

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