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

275 lines 10.6 kB
import { convertToJsonApiUrl } from '../utils/url-converter.js'; import { httpClient } from '../utils/http-client.js'; import { logger } from '../utils/logger.js'; import { PROCESSING_LIMITS, SEARCH_DEPTH_LIMITS } from '../utils/constants.js'; /** * 查找相似API */ export async function handleFindSimilarApis(apiUrl, searchDepth = 'medium', filterByCategory, includeAlternatives = true) { try { logger.info(`Finding similar APIs for: ${apiUrl}`); const jsonApiUrl = convertToJsonApiUrl(apiUrl); // Check if conversion failed if (!jsonApiUrl) { throw new Error('Invalid Apple Developer Documentation URL'); } const response = await httpClient.getJson(jsonApiUrl); // Handle response structure - check if data is wrapped let data; let references; if (response.data) { // Response has a data property, extract it data = response.data; references = response.references || data.references; } else { // Response is the data itself data = response; references = data.references; } // 收集相似API const similarApis = []; // 1. 从"另请参阅"部分收集 if (data.seeAlsoSections) { const seeAlsoApis = extractSeeAlsoApis(data.seeAlsoSections, references, filterByCategory); similarApis.push(...seeAlsoApis); } // 2. 从主题部分收集(medium 和 deep 模式) if (searchDepth === 'medium' || searchDepth === 'deep') { if (data.topicSections && includeAlternatives) { const topicApis = extractTopicApis(data.topicSections, references, filterByCategory); similarApis.push(...topicApis); } } // 3. 深度搜索相关API(deep 模式) if (searchDepth === 'deep') { const deepApis = await extractDeepRelatedApis(similarApis.slice(0, PROCESSING_LIMITS.MAX_SIMILAR_APIS_FOR_DEEP_SEARCH)); // 限制前3个 similarApis.push(...deepApis); } // 去重和评分 const uniqueApis = deduplicateAndScore(similarApis); // 按相似度排序 uniqueApis.sort((a, b) => b.confidence - a.confidence); // 限制结果数量 const maxResults = SEARCH_DEPTH_LIMITS[searchDepth] || SEARCH_DEPTH_LIMITS.medium; const limitedApis = uniqueApis.slice(0, maxResults); // Get the title from data const title = data.title || data.metadata?.title || data.identifier?.split('/').pop() || 'API'; return formatSimilarApis(apiUrl, limitedApis, title, data); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('Invalid Apple Developer Documentation URL')) { throw error; } throw new Error(errorMessage); } } /** * 从"另请参阅"部分提取API */ function extractSeeAlsoApis(seeAlsoSections, references, filterByCategory) { const apis = []; for (const section of seeAlsoSections) { // 过滤分类 if (filterByCategory && !section.title.toLowerCase().includes(filterByCategory.toLowerCase())) { continue; } for (const identifier of section.identifiers) { const api = createSimilarApi(identifier, section.title, 'See Also', 8, // 高相似度 references); if (api) { apis.push(api); } } } return apis; } /** * 从主题部分提取API */ function extractTopicApis(topicSections, references, filterByCategory) { const apis = []; for (const section of topicSections) { // 过滤分类 if (filterByCategory && !section.title.toLowerCase().includes(filterByCategory.toLowerCase())) { continue; } // 限制每个主题的API数量 const limitedIdentifiers = section.identifiers.slice(0, PROCESSING_LIMITS.MAX_TOPIC_IDENTIFIERS); for (const identifier of limitedIdentifiers) { const api = createSimilarApi(identifier, section.title, 'Topic Group', 6, // 中等相似度 references); if (api) { apis.push(api); } } } return apis; } /** * 深度搜索相关API */ async function extractDeepRelatedApis(seedApis) { const deepApis = []; for (const seedApi of seedApis) { try { const jsonApiUrl = convertToJsonApiUrl(seedApi.url); if (!jsonApiUrl) { logger.warn(`Failed to convert URL: ${seedApi.url}`); continue; } const response = await httpClient.getJson(jsonApiUrl); // Handle response structure let data; let references; if (response.data) { data = response.data; references = response.references || data.references; } else { data = response; references = data.references; } // 只从"另请参阅"部分获取,避免过度扩展 if (data.seeAlsoSections) { const relatedApis = extractSeeAlsoApis(data.seeAlsoSections, references); // 降低相似度评分 relatedApis.forEach(api => { api.confidence = Math.max(api.confidence - 2, 3); api.similarityType = 'Deep Related'; }); deepApis.push(...relatedApis); } } catch (error) { logger.error(`Failed to fetch deep related API ${seedApi.url}:`, error); } } return deepApis; } /** * 创建相似API对象 */ function createSimilarApi(identifier, category, similarityType, confidence, references) { if (references?.[identifier]) { const ref = references[identifier]; return { title: ref.title || 'Unknown', url: ref.url ? `https://developer.apple.com${ref.url}` : '#', identifier, abstract: ref.abstract ? ref.abstract.map((a) => a.text || '').join(' ').trim() : undefined, category, similarityType, symbolKind: ref.kind || ref.symbolKind, platforms: ref.platforms ? ref.platforms.map((p) => p.name) : undefined, confidence, }; } // 如果references中没有,尝试从标识符解析 if (identifier.startsWith('doc://')) { const parts = identifier.split('/'); const apiName = parts[parts.length - 1] || 'Unknown'; const pathPart = identifier.replace(/^doc:\/\/[^\/]+\/documentation\//, ''); return { title: apiName, url: `https://developer.apple.com/documentation/${pathPart}`, identifier, category, similarityType, confidence: confidence - 1, // 略降相似度 }; } return null; } /** * 去重和评分 */ function deduplicateAndScore(apis) { const apiMap = new Map(); for (const api of apis) { const existing = apiMap.get(api.identifier); if (existing) { // 如果已存在,保留评分更高的 if (api.confidence > existing.confidence) { apiMap.set(api.identifier, api); } } else { apiMap.set(api.identifier, api); } } return Array.from(apiMap.values()); } /** * 格式化相似API结果 */ function formatSimilarApis(originalUrl, similarApis, originalApiName, originalData) { const apiName = originalApiName || new URL(originalUrl).pathname.split('/').pop() || 'API'; let content = `# Similar APIs to ${apiName}\n\n`; if (similarApis.length === 0) { content += 'No similar APIs found'; return content; } // Add metadata about the original API if available if (originalData?.metadata) { const roleHeading = originalData.metadata.roleHeading || ''; const platforms = originalData.metadata.platforms?.map(p => `${p.name} ${p.introducedAt || ''}+`).join(', ') || ''; if (roleHeading || platforms) { content += `${roleHeading}${platforms ? ' · ' + platforms : ''}\n\n`; } } content += `**Source:** [${originalUrl}](${originalUrl})\n\n`; content += `**Found ${similarApis.length} similar APIs (sorted by relevance):**\n\n`; // Group by category instead of similarity type for better organization const groupedByCategory = new Map(); for (const api of similarApis) { const key = `${api.similarityType}: ${api.category}`; if (!groupedByCategory.has(key)) { groupedByCategory.set(key, []); } groupedByCategory.get(key).push(api); } for (const [categoryKey, apis] of groupedByCategory) { content += `## ${categoryKey}\n\n`; for (const api of apis) { content += formatSingleSimilarApi(api); } } // 相似度分析 content += '## Similarity Analysis\n\n'; const avgConfidence = similarApis.reduce((sum, api) => sum + api.confidence, 0) / similarApis.length; content += `**Average Similarity:** ${avgConfidence.toFixed(1)}/10\n`; const highConfidenceApis = similarApis.filter(api => api.confidence >= 7); if (highConfidenceApis.length > 0) { content += `**Highly Similar APIs:** ${highConfidenceApis.length}\n`; } const categories = [...new Set(similarApis.map(api => api.category))]; content += `**Categories:** ${categories.join(', ')}\n\n`; content += `---\n\n*Total: ${similarApis.length} similar APIs found*`; return content; } /** * 格式化单个相似API */ function formatSingleSimilarApi(api) { let content = `### [${api.title}](${api.url})\n`; if (api.abstract) { content += `${api.abstract}\n\n`; } // 添加元数据 const metadata = [`Similarity: ${api.confidence}/10`]; if (api.symbolKind) { metadata.push(`Type: ${api.symbolKind}`); } if (api.category !== api.similarityType) { metadata.push(`Category: ${api.category}`); } content += `*${metadata.join(' | ')}*\n\n`; // 添加平台信息 if (api.platforms && api.platforms.length > 0) { content += `**Platforms:** ${api.platforms.join(', ')}\n\n`; } return content; } //# sourceMappingURL=find-similar-apis.js.map