UNPKG

@kimsungwhee/apple-docs-mcp

Version:

MCP server for Apple Developer Documentation - Search iOS/macOS/SwiftUI/UIKit docs, WWDC videos, Swift/Objective-C APIs & code examples in Claude, Cursor & AI assistants

188 lines (184 loc) โ€ข 6.25 kB
import * as cheerio from 'cheerio'; import { parseSearchResult } from './search-result-parser.js'; import { API_LIMITS } from '../utils/constants.js'; import { logger } from '../utils/logger.js'; /** * Formats search results for display */ export function formatSearchResults(results, query, filterType, searchUrl) { let content = ''; // Add header content += '# Apple Documentation Search Results\n\n'; content += `**Query:** "${query}"\n`; content += `**Filter:** ${filterType}\n`; content += `**Results found:** ${results.length}\n\n`; // Check if query might be video-related const videoSuggestion = getVideoSuggestion(query); if (videoSuggestion) { content += videoSuggestion; } if (results.length === 0) { content += formatNoResultsMessage(query, filterType, searchUrl); return content; } // Group results by type const groupedResults = groupResultsByType(results); // Format each group Object.entries(groupedResults).forEach(([type, typeResults]) => { content += formatResultGroup(type, typeResults); }); // Add footer content += formatSearchFooter(searchUrl); return content; } /** * Format no results message */ function formatNoResultsMessage(query, filterType, searchUrl) { let content = '## No Results Found\n\n'; content += `No ${filterType === 'all' ? '' : filterType + ' '}results found for "${query}".\n\n`; content += '### Suggestions:\n'; content += '- Try using different keywords\n'; content += '- Check spelling\n'; content += '- Use more general terms\n'; content += '- Try searching for framework names (e.g., "SwiftUI", "UIKit")\n'; // Add video-specific suggestion if applicable const videoSuggestion = getVideoSuggestion(query); if (videoSuggestion) { content += '- For WWDC videos, use the dedicated WWDC tools\n'; } content += `\n[View search on Apple Developer](${searchUrl})`; return content; } /** * Group results by type */ function groupResultsByType(results) { const groups = {}; results.forEach(result => { const displayType = getDisplayType(result.type); if (!groups[displayType]) { groups[displayType] = []; } groups[displayType].push(result); }); return groups; } /** * Get display type for result */ function getDisplayType(type) { const typeDisplayNames = { 'documentation': '๐Ÿ“š API Documentation', 'documentation-article': '๐Ÿ“„ Articles', 'documentation-tutorial': '๐Ÿ“– Tutorials', 'sample-code': '๐Ÿ’ป Sample Code', 'guide': '๐Ÿ“‹ Guides', }; return typeDisplayNames[type] || '๐Ÿ“ Other'; } /** * Format a group of results */ function formatResultGroup(type, results) { let content = `## ${type}\n\n`; results.forEach((result, index) => { content += formatSingleResult(result, index + 1); }); return content; } /** * Format a single search result */ function formatSingleResult(result, index) { let content = `### ${index}. ${result.title}`; // Add badges const badges = []; if (result.beta) { badges.push('๐Ÿงช Beta'); } if (badges.length > 0) { content += ` ${badges.join(' ')}`; } content += '\n\n'; // Add metadata if (result.framework) { content += `**Framework:** ${result.framework}\n`; } content += `**Type:** ${result.type.replace(/-/g, ' ')}\n`; // Add description if (result.description) { content += `**Description:** ${result.description}\n`; } // Add URL content += `**URL:** ${result.url}\n\n`; return content; } /** * Format search footer */ function formatSearchFooter(searchUrl) { return `---\n\n[View all results on Apple Developer](${searchUrl})`; } /** * Check if query might be video-related and provide WWDC tool suggestions */ function getVideoSuggestion(query) { const videoKeywords = [ 'video', 'wwdc', 'session', 'presentation', 'talk', 'keynote', 'demo', 'tutorial', 'walkthrough', 'overview', 'introduction', 'deep dive', 'best practices', 'tips', 'tricks', ]; const queryLower = query.toLowerCase(); const hasVideoKeyword = videoKeywords.some(keyword => queryLower.includes(keyword)); // Also check for year patterns (e.g., "2024", "2025", "wwdc24") const hasYearPattern = /\b(20[2-9][0-9]|wwdc[2-9][0-9])\b/i.test(query); if (hasVideoKeyword || hasYearPattern) { return `## ๐Ÿ’ก Looking for WWDC Videos? This search covers documentation and samples, but not WWDC videos. For WWDC content, try these tools: - **\`list_wwdc_videos\`** - Browse WWDC videos by year, topic, or code availability - **\`search_wwdc_content\`** - Search through video transcripts and code examples - **\`browse_wwdc_topics\`** - Explore videos organized by topic categories --- `; } return null; } /** * Parse search results with reduced complexity */ export function parseSearchResults(html, query, searchUrl, filterType = 'all') { try { const $ = cheerio.load(html); const results = []; // Parse each search result (with limit) $('.search-result').each((_, element) => { if (results.length >= API_LIMITS.MAX_SEARCH_RESULTS) { return false; // Stop parsing when limit reached } const result = parseSearchResult($(element), filterType); if (result) { results.push(result); } return true; // Continue parsing }); // Format results const formattedContent = formatSearchResults(results, query, filterType, searchUrl); return { content: [{ type: 'text', text: formattedContent, }], }; } catch (error) { logger.error('Error parsing search results:', error); return { content: [{ type: 'text', text: `Error parsing search results: ${error instanceof Error ? error.message : 'Unknown error'}`, }], }; } } //# sourceMappingURL=search-parser.js.map