find_related_wwdc_videos
Find related WWDC sessions to build learning paths. Discover prerequisites, follow-up content, and thematically similar talks for comprehensive developer education.
Instructions
Discover WWDC sessions related to a specific video. Finds prerequisite sessions, follow-up content, and thematically similar talks. Essential for creating learning paths.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| videoId | Yes | Source video ID. Example: "10101" for keynote. | |
| year | Yes | Source video year. Example: "2025" | |
| includeExplicitRelated | No | Include Apple's recommended related videos. Usually prerequisites or follow-ups. Default: true | |
| includeTopicRelated | No | Include videos from same topic categories. Good for comprehensive learning. Default: true | |
| includeYearRelated | No | Include other videos from same WWDC. Default: false | |
| limit | No | Max related videos (default: 15). |
Implementation Reference
- src/tools/wwdc/wwdc-handlers.ts:822-986 (handler)The main handler function `handleFindRelatedWWDCVideos` that implements the tool logic. It loads the source video, finds related videos through three strategies: (1) explicit related videos from video metadata, (2) topic-related videos by matching topics, and (3) year-related videos from the same WWDC year. Results are scored, sorted, and formatted as markdown output.
export async function handleFindRelatedWWDCVideos( videoId: string, year: string, includeExplicitRelated: boolean = true, includeTopicRelated: boolean = true, includeYearRelated: boolean = false, limit: number = 15, ): Promise<string> { try { // Load the source video const sourceVideo = await loadVideoData(year, videoId); let content = `# Related Videos for "${sourceVideo.title}"\n\n`; content += `**Source:** [${sourceVideo.title}](${sourceVideo.url}) (WWDC${year})\n\n`; const relatedVideos: Array<{ video: WWDCVideo & { year: string }; relationship: string; score: number; }> = []; // 1. Explicit related videos from video metadata if (includeExplicitRelated && sourceVideo.relatedVideos) { for (const related of sourceVideo.relatedVideos) { try { const relatedVideo = await loadVideoData(related.year, related.id); relatedVideos.push({ video: { ...relatedVideo, year: related.year }, relationship: 'Explicitly related', score: 10, }); } catch (error) { logger.warn(`Failed to load related video ${related.year}-${related.id}:`, error); } } } // 2. Topic-related videos if (includeTopicRelated && sourceVideo.topics && sourceVideo.topics.length > 0) { for (const topic of sourceVideo.topics) { try { // Try to find topic by name mapping const metadata = await loadGlobalMetadata(); const topicEntry = metadata.topics.find(t => t.name.toLowerCase() === topic.toLowerCase() || t.id.toLowerCase().includes(topic.toLowerCase().replace(/\s+/g, '-')), ); if (topicEntry) { const topicIndex = await loadTopicIndex(topicEntry.id); // Get videos from same topic (excluding source video) const topicVideos = topicIndex.videos.filter(v => v.id !== videoId); // Load video data for scoring const videoFiles = topicVideos.slice(0, 10).map((v: any) => v.dataFile); // Limit to avoid too many requests const videos = await loadVideosData(videoFiles); for (const video of videos) { // Skip if already added if (relatedVideos.find(r => r.video.id === video.id && r.video.year === video.year)) { continue; } // Calculate similarity score based on shared topics const sharedTopics = (sourceVideo.topics || []).filter(t => video.topics?.some(vt => vt.toLowerCase() === t.toLowerCase()) || false, ); const score = sharedTopics.length * 2; relatedVideos.push({ video: { ...video, year: video.year }, relationship: `Same topic: ${topicEntry.name}`, score, }); } } } catch (error) { logger.warn(`Failed to find topic-related videos for topic ${topic}:`, error); } } } // 3. Year-related videos (same year, similar topics) if (includeYearRelated) { try { const yearIndex = await loadYearIndex(year); // Get videos from same year with overlapping topics const yearVideos = yearIndex.videos.filter(v => v.id !== videoId && v.topics.some(t => sourceVideo.topics?.some(st => st.toLowerCase() === t.toLowerCase()) || false), ); // Load a sample of videos const videoFiles = yearVideos.slice(0, 10).map((v: any) => v.dataFile); const videos = await loadVideosData(videoFiles); for (const video of videos) { // Skip if already added if (relatedVideos.find(r => r.video.id === video.id && r.video.year === video.year)) { continue; } const sharedTopics = (sourceVideo.topics || []).filter(t => video.topics?.some(vt => vt.toLowerCase() === t.toLowerCase()) || false, ); const score = sharedTopics.length; relatedVideos.push({ video: { ...video, year: video.year }, relationship: `Same year, shared topics: ${sharedTopics.join(', ')}`, score, }); } } catch (error) { logger.warn('Failed to find year-related videos:', error); } } // Sort by score (descending) and apply limit relatedVideos.sort((a, b) => b.score - a.score); const limitedResults = relatedVideos.slice(0, limit); if (limitedResults.length === 0) { content += 'No related videos found.\n'; } else { content += `## Related Videos (${limitedResults.length})\n\n`; limitedResults.forEach(result => { content += `### [${result.video.title}](${result.video.url})\n`; content += `*WWDC${result.video.year} | ${result.relationship}*\n\n`; const features: string[] = []; if (result.video.duration) { features.push(`Duration: ${result.video.duration}`); } if (result.video.hasTranscript) { features.push('Transcript'); } if (result.video.hasCode) { features.push('Code'); } if (features.length > 0) { content += `${features.join(' | ')}\n`; } if (result.video.topics.length > 0) { content += `**Topics:** ${result.video.topics.join(', ')}\n`; } content += '\n'; }); } return content; } catch (error) { logger.error('Failed to find related WWDC videos:', error); const errorMessage = error instanceof Error ? error.message : String(error); return `Error: Failed to find related WWDC videos: ${errorMessage}`; } } - src/schemas/wwdc.schemas.ts:60-69 (schema)Zod schema `findRelatedWWDCVideosSchema` that validates input parameters: videoId (required string), year (required string), includeExplicitRelated (boolean, default true), includeTopicRelated (boolean, default true), includeYearRelated (boolean, default false), and limit (number 1-50, default 15).
* Schema for find_related_wwdc_videos */ export const findRelatedWWDCVideosSchema = z.object({ videoId: z.string().describe('Video ID to find related videos for'), year: z.string().describe('Year of the source video'), includeExplicitRelated: z.boolean().default(true).describe('Include explicitly related videos'), includeTopicRelated: z.boolean().default(true).describe('Include videos from same topics'), includeYearRelated: z.boolean().default(false).describe('Include videos from same year'), limit: z.number().min(1).max(50).default(15).describe('Maximum number of related videos'), }); - src/tools/definitions.ts:422-455 (registration)Tool registration definition for 'find_related_wwdc_videos' with description and JSON Schema input schema. Defines the tool's metadata, required parameters (videoId, year), and optional parameters with their descriptions.
{ name: 'find_related_wwdc_videos', description: 'Discover WWDC sessions related to a specific video. Finds prerequisite sessions, follow-up content, and thematically similar talks. Essential for creating learning paths.', inputSchema: { type: 'object', properties: { videoId: { type: 'string', description: 'Source video ID. Example: "10101" for keynote.', }, year: { type: 'string', description: 'Source video year. Example: "2025"', }, includeExplicitRelated: { type: 'boolean', description: 'Include Apple\'s recommended related videos. Usually prerequisites or follow-ups. Default: true', }, includeTopicRelated: { type: 'boolean', description: 'Include videos from same topic categories. Good for comprehensive learning. Default: true', }, includeYearRelated: { type: 'boolean', description: 'Include other videos from same WWDC. Default: false', }, limit: { type: 'number', description: 'Max related videos (default: 15).', }, }, required: ['videoId', 'year'], }, }, - src/tools/handlers.ts:291-302 (handler)Handler wrapper in the toolHandlers map that validates incoming arguments using the Zod schema, then delegates to handleFindRelatedWWDCVideos with the parsed parameters, returning the result as MCP content.
find_related_wwdc_videos: async (args, _server) => { const validatedArgs = findRelatedWWDCVideosSchema.parse(args); const result = await handleFindRelatedWWDCVideos( validatedArgs.videoId, validatedArgs.year, validatedArgs.includeExplicitRelated, validatedArgs.includeTopicRelated, validatedArgs.includeYearRelated, validatedArgs.limit, ); return { content: [{ type: 'text', text: result }] }; },