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

306 lines 11.9 kB
import { technologyOverviewsCache, generateUrlCacheKey } from '../utils/cache.js'; import { APPLE_URLS } from '../utils/constants.js'; import { httpClient } from '../utils/http-client.js'; import { logger } from '../utils/logger.js'; /** * Handle get technology overviews */ export async function handleGetTechnologyOverviews(category, platform = 'all', searchQuery, includeSubcategories = true, limit = 50) { try { logger.info('Fetching technology overviews...'); // Generate cache key const cacheKey = generateUrlCacheKey('technology-overviews', { category, platform, searchQuery, includeSubcategories, limit, }); // Try to get from cache first const cachedResult = technologyOverviewsCache.get(cacheKey); if (cachedResult) { logger.debug('Technology overviews cache hit'); return cachedResult; } // Fetch both data files const [overviewsData, overviewsIndex] = await Promise.all([ httpClient.getJson(APPLE_URLS.TECHNOLOGY_OVERVIEWS_JSON), httpClient.getJson(APPLE_URLS.TECHNOLOGY_OVERVIEWS_INDEX_JSON), ]); // Parse and filter overviews const overviews = parseOverviews(overviewsData, overviewsIndex); const filteredOverviews = applyOverviewsFilters(overviews, { category, platform, searchQuery, includeSubcategories, limit, }); // Format output const result = formatOverviewsList(filteredOverviews); // Cache the result technologyOverviewsCache.set(cacheKey, result); return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : JSON.stringify(error); logger.error('Error fetching technology overviews:', error); return `Error: Failed to fetch technology overviews: ${errorMessage}`; } } /** * Parse overviews data */ function parseOverviews(overviewsData, overviewsIndex) { const overviews = []; // Process main overview sections if (overviewsData.topicSections) { overviewsData.topicSections.forEach(section => { section.identifiers.forEach(identifier => { const reference = overviewsData.references[identifier]; if (reference) { // Construct description from abstract array let description = ''; if (reference.abstract && Array.isArray(reference.abstract)) { description = reference.abstract .map(part => part.text || '') .join('') .trim(); } const overviewItem = { title: reference.title, url: reference.url ? `https://developer.apple.com${reference.url}` : '', description, category: extractCategoryFromUrl(reference.url), type: reference.kind ?? 'overview', identifier, sectionTitle: section.title, depth: 0, }; overviews.push(overviewItem); } }); }); } // Process index data for hierarchical structure if (overviewsIndex.interfaceLanguages?.swift) { processIndexSection(overviewsIndex.interfaceLanguages.swift, overviews, 0, null); } return overviews; } /** * Process index section recursively */ function processIndexSection(sections, overviews, depth, parentSection) { let currentSection = parentSection; sections.forEach(section => { // Track current section from group markers if (section.type === 'groupMarker') { currentSection = section.title; // Process children with same depth but updated section if (section.children) { processIndexSection(section.children, overviews, depth, currentSection); } return; } // Check if this section already exists in overviews const existingOverview = overviews.find(o => o.url.includes(section.path) || o.title === section.title); if (existingOverview) { // Update with additional info existingOverview.path = section.path; existingOverview.depth = depth; existingOverview.type = section.type; if (currentSection && !existingOverview.sectionTitle) { existingOverview.sectionTitle = currentSection; } } else { // Add new overview item const newOverview = { title: section.title, url: `https://developer.apple.com${section.path}`, description: '', category: extractCategoryFromUrl(section.path), type: section.type, identifier: section.path, path: section.path, depth, sectionTitle: currentSection || '', }; overviews.push(newOverview); } // Process children if (section.children) { processIndexSection(section.children, overviews, depth + 1, currentSection); } }); } /** * Extract category from URL */ function extractCategoryFromUrl(url) { const match = url.match(/\/documentation\/technologyoverviews\/([^/]+)/i); return match?.[1] ?? 'general'; } /** * Apply filters to overviews */ function applyOverviewsFilters(overviews, filters) { let filtered = [...overviews]; // Category filter if (filters.category) { const categoryLower = filters.category.toLowerCase(); // First, find all items that match the category const categoryItems = overviews.filter(overview => { return overview.category.toLowerCase() === categoryLower || overview.title.toLowerCase() === categoryLower.replace(/-/g, ' ') || overview.path?.toLowerCase().includes(`/${categoryLower}`); }); // If we found category items, also include their children if (categoryItems.length > 0) { const categoryPaths = categoryItems.map(item => item.path || item.url); filtered = overviews.filter(overview => { // Include the category items themselves if (categoryItems.includes(overview)) { return true; } // Include children of category items if (overview.path || overview.url) { const overviewPath = (overview.path || overview.url).replace('https://developer.apple.com', ''); return categoryPaths.some(categoryPath => { const cleanCategoryPath = categoryPath.replace('https://developer.apple.com', ''); return overviewPath.startsWith(cleanCategoryPath) && overviewPath !== cleanCategoryPath; }); } return false; }); } else { // No matching category found filtered = []; } } // Platform filter if (filters.platform && filters.platform !== 'all') { const platformLower = filters.platform.toLowerCase(); filtered = filtered.filter(overview => overview.title.toLowerCase().includes(platformLower) || overview.description.toLowerCase().includes(platformLower) || overview.path?.toLowerCase().includes(platformLower)); } // Search query filter if (filters.searchQuery) { const queryLower = filters.searchQuery.toLowerCase(); filtered = filtered.filter(overview => overview.title.toLowerCase().includes(queryLower) || overview.description.toLowerCase().includes(queryLower)); } // If not including subcategories, filter out deep items if (!filters.includeSubcategories) { // Keep only top-level items (depth 0 or 1) filtered = filtered.filter(overview => overview.depth <= 1); } // Apply limit (limit top-level items, but include their children) if (filters.limit && filters.limit > 0) { // Count top-level items let topLevelCount = 0; const limitedResults = []; for (const item of filtered) { if (item.depth === 0) { if (topLevelCount >= filters.limit) { break; } topLevelCount++; } limitedResults.push(item); } filtered = limitedResults; } return filtered; } /** * Format overviews list */ function formatOverviewsList(overviews) { if (overviews.length === 0) { return 'No technology overviews found matching the specified criteria.'; } let content = '# Apple Developer Technology Overviews\n\n'; content += '*Comprehensive guides for Apple platforms and technologies*\n\n'; // Group by category and depth for better organization const grouped = groupOverviewsByCategory(overviews); // Format each category Object.entries(grouped).forEach(([category, items]) => { const categoryTitle = formatCategoryTitle(category); content += `## ${categoryTitle}\n\n`; // Sort items by depth and title items.sort((a, b) => { if (a.depth !== b.depth) { return a.depth - b.depth; } return a.title.localeCompare(b.title); }); // Format items with proper indentation items.forEach(item => { const indent = ' '.repeat(item.depth); // Title with link content += `${indent}### [${item.title}](${item.url})\n`; // Type badge if (item.type && item.type !== 'overview') { content += `${indent}*Type: ${formatType(item.type)}*\n`; } // Description if (item.description) { content += `${indent}${item.description}\n`; } content += '\n'; }); }); content += '\n---\n\n[Explore all Technology Overviews](https://developer.apple.com/documentation/technologyoverviews)'; return content; } /** * Group overviews by category */ function groupOverviewsByCategory(overviews) { return overviews.reduce((acc, overview) => { // Use sectionTitle for top-level items, otherwise use the parent category let groupKey = overview.sectionTitle; // If no section title or it's a nested item, try to group by top-level category if (!groupKey && overview.depth > 0) { // Extract top-level category from the path const pathParts = overview.category.split('-'); groupKey = formatCategoryTitle(pathParts[0]); } // Default to formatted category if (!groupKey) { groupKey = formatCategoryTitle(overview.category); } if (!acc[groupKey]) { acc[groupKey] = []; } acc[groupKey].push(overview); return acc; }, {}); } /** * Format category title */ function formatCategoryTitle(category) { return category .replace(/-/g, ' ') .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); } /** * Format type */ function formatType(type) { const typeMap = { 'collection': 'Collection', 'article': 'Article', 'module': 'Module', 'groupMarker': 'Group', }; return typeMap[type] ?? type; } //# sourceMappingURL=get-technology-overviews.js.map