@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
JavaScript
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