get_watch_history
Retrieve detailed watch history and session data from Plex Media Server, enabling filtering by user, media type, and session count for precise insights.
Instructions
Get detailed watch history with session information
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Number of sessions to return (default: 50) | |
| mediaType | No | Filter by media type | all |
| userId | No | Filter by specific user ID (optional) |
Implementation Reference
- src/index.ts:1197-1341 (handler)The primary handler function implementing the get_watch_history tool logic. Fetches session history from Plex API (/status/sessions/history/all), applies filters for user/mediaType, computes progress/completion, returns formatted JSON response. Includes fallback using library metadata if direct history unavailable.private async getWatchHistory(limit: number, userId?: string, mediaType: string = "all") { try { // Try primary watch history endpoint const params: Record<string, any> = { "X-Plex-Container-Size": limit, sort: "date:desc", }; if (userId) { params.accountID = userId; } const data = await this.makeRequest("/status/sessions/history/all", params); let sessions = data.MediaContainer?.Metadata || []; // Filter by media type if specified if (mediaType !== "all") { const typeMap: Record<string, string> = { movie: "movie", show: "show", episode: "episode" }; sessions = sessions.filter((session: any) => session.type === typeMap[mediaType]); } return { content: [ { type: "text", text: JSON.stringify({ watchHistory: sessions.map((session: any) => ({ sessionKey: session.sessionKey, ratingKey: session.ratingKey, title: session.title, type: session.type, year: session.year, viewedAt: session.viewedAt, duration: session.duration, viewOffset: session.viewOffset, user: session.User?.title, player: session.Player?.title, platform: session.Player?.platform, progress: session.viewOffset && session.duration ? Math.round((session.viewOffset / session.duration) * 100) : 0, completed: session.viewOffset && session.duration ? session.viewOffset >= (session.duration * 0.9) : false, })), totalSessions: sessions.length, }, null, 2), }, ], }; } catch (error) { // Fallback: Create pseudo-history from library metadata try { const librariesData = await this.makeRequest("/library/sections"); const libraries = librariesData.MediaContainer?.Directory || []; let allViewedItems: any[] = []; for (const library of libraries) { try { const params: Record<string, any> = { "X-Plex-Container-Size": Math.ceil(limit / libraries.length) + 5, sort: "lastViewedAt:desc", }; if (mediaType !== "all") { params.type = this.getPlexTypeId(mediaType); } const contentData = await this.makeRequest(`/library/sections/${library.key}/all`, params); const content = contentData.MediaContainer?.Metadata || []; // Filter for items that have been viewed const viewedContent = content.filter((item: any) => item.lastViewedAt && item.viewCount > 0 ); // Convert to history-like format const historyItems = viewedContent.map((item: any) => ({ ratingKey: item.ratingKey, title: item.title, type: item.type, year: item.year, lastViewedAt: item.lastViewedAt, viewCount: item.viewCount, duration: item.duration, viewOffset: item.viewOffset || 0, library: library.title, })); allViewedItems.push(...historyItems); } catch (libError) { continue; } } // Sort by last viewed date and take requested number allViewedItems.sort((a: any, b: any) => (b.lastViewedAt || 0) - (a.lastViewedAt || 0)); allViewedItems = allViewedItems.slice(0, limit); return { content: [ { type: "text", text: JSON.stringify({ watchHistory: allViewedItems.map((item: any) => ({ ratingKey: item.ratingKey, title: item.title, type: item.type, year: item.year, lastViewedAt: item.lastViewedAt, viewCount: item.viewCount, duration: item.duration, viewOffset: item.viewOffset, library: item.library, progress: item.viewOffset && item.duration ? Math.round((item.viewOffset / item.duration) * 100) : 0, completed: item.viewOffset && item.duration ? item.viewOffset >= (item.duration * 0.85) : item.viewCount > 0, })), totalSessions: allViewedItems.length, note: "Generated from library metadata (fallback method)", }, null, 2), }, ], }; } catch (fallbackError) { return { content: [ { type: "text", text: JSON.stringify({ error: "Watch history not available", message: "Unable to retrieve watch history from this Plex server", suggestion: "This feature may require Plex Pass or detailed logging to be enabled", watchHistory: [], }, null, 2), }, ], }; } } }
- src/index.ts:232-255 (schema)Input schema definition for the get_watch_history tool, specifying parameters: limit (number, default 50), userId (optional string), mediaType (enum: movie/show/episode/all, default 'all').{ name: "get_watch_history", description: "Get detailed watch history with session information", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Number of sessions to return (default: 50)", default: 50, }, userId: { type: "string", description: "Filter by specific user ID (optional)", }, mediaType: { type: "string", description: "Filter by media type", enum: ["movie", "show", "episode", "all"], default: "all", }, }, }, },
- src/index.ts:305-310 (registration)Tool registration in the central CallToolRequestSchema switch dispatcher: maps 'get_watch_history' calls to the getWatchHistory handler with parsed arguments.case "get_watch_history": return await this.getWatchHistory( ((args as any)?.limit as number) || 50, (args as any)?.userId as string, (args as any)?.mediaType as string || "all" );
- src/index.ts:44-257 (registration)Tool registration in ListToolsRequestSchema handler: includes get_watch_history in the advertised tools list with name, description, and inputSchema.this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "get_libraries", description: "Get all Plex libraries", inputSchema: { type: "object", properties: {}, }, }, { name: "search_media", description: "Search for media in Plex libraries", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query", }, type: { type: "string", description: "Media type (movie, show, episode, artist, album, track)", enum: ["movie", "show", "episode", "artist", "album", "track"], }, }, required: ["query"], }, }, { name: "get_recently_added", description: "Get recently added media", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Number of items to return (default: 10)", default: 10, }, }, }, }, { name: "get_on_deck", description: "Get on deck (continue watching) items", inputSchema: { type: "object", properties: {}, }, }, { name: "get_media_details", description: "Get detailed information about a specific media item", inputSchema: { type: "object", properties: { ratingKey: { type: "string", description: "The rating key of the media item", }, }, required: ["ratingKey"], }, }, { name: "get_recently_watched", description: "Get recently watched movies and shows", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Number of items to return (default: 25)", default: 25, }, mediaType: { type: "string", description: "Filter by media type (movie, show, episode, all)", enum: ["movie", "show", "episode", "all"], default: "all", }, }, }, }, { name: "get_fully_watched", description: "Get all fully watched movies and shows from a library", inputSchema: { type: "object", properties: { libraryKey: { type: "string", description: "Library section key (optional, searches all if not provided)", }, mediaType: { type: "string", description: "Filter by media type (movie, show, all)", enum: ["movie", "show", "all"], default: "all", }, limit: { type: "number", description: "Number of items to return (default: 100)", default: 100, }, }, }, }, { name: "get_watch_stats", description: "Get comprehensive watch statistics (Tautulli-style analytics)", inputSchema: { type: "object", properties: { timeRange: { type: "number", description: "Time range in days (default: 30)", default: 30, }, statType: { type: "string", description: "Type of statistics to retrieve", enum: ["plays", "duration", "users", "libraries", "platforms"], default: "plays", }, }, }, }, { name: "get_user_stats", description: "Get user-specific watch statistics", inputSchema: { type: "object", properties: { timeRange: { type: "number", description: "Time range in days (default: 30)", default: 30, }, }, }, }, { name: "get_library_stats", description: "Get library-specific statistics", inputSchema: { type: "object", properties: { libraryKey: { type: "string", description: "Library section key (optional)", }, }, }, }, { name: "get_popular_content", description: "Get most popular content by plays or duration", inputSchema: { type: "object", properties: { timeRange: { type: "number", description: "Time range in days (default: 30)", default: 30, }, metric: { type: "string", description: "Sort by plays or total duration", enum: ["plays", "duration"], default: "plays", }, mediaType: { type: "string", description: "Filter by media type", enum: ["movie", "show", "episode", "all"], default: "all", }, limit: { type: "number", description: "Number of items to return (default: 10)", default: 10, }, }, }, }, { name: "get_watch_history", description: "Get detailed watch history with session information", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Number of sessions to return (default: 50)", default: 50, }, userId: { type: "string", description: "Filter by specific user ID (optional)", }, mediaType: { type: "string", description: "Filter by media type", enum: ["movie", "show", "episode", "all"], default: "all", }, }, }, }, ], };