apple-dev-mcp
Version:
Complete Apple development guidance: Human Interface Guidelines (design) + Technical Documentation for iOS, macOS, watchOS, tvOS, and visionOS
453 lines • 18.9 kB
JavaScript
/**
* Apple Content API Client
*
* Unified client for Apple's developer documentation and Human Interface Guidelines
* Direct API access to Apple's developer documentation at developer.apple.com/tutorials/data
* Adapted from MightyDillah's apple-doc-mcp with enhancements for HIG integration
*/
import axios from 'axios';
const BASE_URL = 'https://developer.apple.com/tutorials/data';
const HEADERS = {
'User-Agent': 'Apple-Dev-MCP/2.0.3 (Development Purpose; Educational Use)',
'Referer': 'https://developer.apple.com/documentation/',
'Accept': 'application/json',
'Accept-Language': 'en-US,en;q=0.9',
'DNT': '1'
};
export class AppleContentAPIClient {
cache;
cacheTimeout = 10 * 60; // 10 minutes cache for API content (in seconds for HIGCache)
constructor(cache) {
this.cache = cache;
}
async makeRequest(url) {
const cacheKey = `api:${url}`;
// Check cache first
const cached = this.cache.get(cacheKey);
if (cached) {
return cached;
}
try {
const response = await axios.get(url, {
headers: HEADERS,
timeout: 15000 // 15 second timeout (matching MightyDillah's approach)
});
// Cache the result for 10 minutes
this.cache.set(cacheKey, response.data, this.cacheTimeout);
return response.data;
}
catch (error) {
throw new Error(`Failed to fetch technical documentation: ${error}`);
}
}
async getTechnologies() {
const url = `${BASE_URL}/documentation/technologies.json`;
const data = await this.makeRequest(url);
return data.references || {};
}
async getFramework(frameworkName) {
const url = `${BASE_URL}/documentation/${frameworkName}.json`;
return await this.makeRequest(url);
}
async getSymbol(path) {
// Remove leading slash if present
const cleanPath = path.startsWith('/') ? path.slice(1) : path;
const url = `${BASE_URL}/${cleanPath}.json`;
return await this.makeRequest(url);
}
/**
* Get technical documentation for a symbol with enhanced formatting
*/
async getTechnicalDocumentation(path) {
const symbolData = await this.getSymbol(path);
const id = path.replace(/[^a-zA-Z0-9]/g, '_');
const framework = this.extractFrameworkFromPath(path);
const abstract = this.extractText(symbolData.abstract);
const codeExamples = this.extractCodeExamples(symbolData);
const relatedSymbols = this.extractRelatedSymbols(symbolData);
return {
id,
symbol: symbolData.metadata.title,
framework,
symbolKind: symbolData.metadata.symbolKind,
platforms: this.formatPlatformList(symbolData.metadata.platforms),
abstract,
apiReference: this.generateAPIReference(symbolData),
codeExamples,
relatedSymbols,
url: `https://developer.apple.com/${path}`,
lastUpdated: new Date()
};
}
/**
* Search across frameworks with direct symbol lookup and framework search
*/
async searchGlobal(query, options = {}) {
const { maxResults = 20, includeRelevanceScore = true } = options;
const results = [];
try {
// First, try direct symbol lookup for common patterns
const directResults = await this.tryDirectSymbolLookup(query, options);
results.push(...directResults);
// If direct lookup didn't find enough results, search frameworks
if (results.length < maxResults) {
const technologies = await this.getTechnologies();
const frameworks = Object.values(technologies).filter(tech => tech.kind === 'symbol' && tech.role === 'collection');
// Prioritize common frameworks (sequential search to avoid API overload)
const prioritizedFrameworks = this.prioritizeFrameworks(frameworks, query);
// Search only top 2 frameworks sequentially to stay fast
for (const framework of prioritizedFrameworks.slice(0, 2)) {
if (results.length >= maxResults)
break;
try {
const frameworkResults = await this.searchFramework(framework.title, query, {
symbolType: options.symbolType,
platform: options.platform,
maxResults: Math.ceil((maxResults - results.length) / 2),
includeRelevanceScore
});
results.push(...frameworkResults);
// Stop early if we have good results
if (results.length >= 10)
break;
}
catch {
// Continue with next framework if one fails
continue;
}
}
}
return results
.sort((a, b) => (b.relevanceScore || 0) - (a.relevanceScore || 0))
.slice(0, maxResults);
}
catch (error) {
throw new Error(`Global technical search failed: ${error}`);
}
}
/**
* Try direct symbol lookup for common Apple symbols
*/
async tryDirectSymbolLookup(query, options) {
const results = [];
// Common symbol patterns to try direct lookup
const symbolPatterns = [
// Exact match
{ framework: this.guessFramework(query), symbol: query },
// UI prefix
{ framework: 'UIKit', symbol: query.startsWith('UI') ? query : `UI${query}` },
// NS prefix
{ framework: 'AppKit', symbol: query.startsWith('NS') ? query : `NS${query}` },
// Common SwiftUI symbols
{ framework: 'SwiftUI', symbol: query }
];
for (const pattern of symbolPatterns) {
if (results.length >= 3)
break; // Limit direct lookups
try {
const symbolPath = `documentation/${pattern.framework.toLowerCase()}/${pattern.symbol.toLowerCase()}`;
const symbolData = await this.getSymbol(symbolPath);
if (symbolData && symbolData.metadata) {
const relevanceScore = options.includeRelevanceScore
? this.calculateDirectSymbolRelevance(symbolData.metadata.title, query)
: 1.0;
results.push({
title: symbolData.metadata.title,
description: this.extractText(symbolData.abstract || []),
path: `/${symbolPath}`,
framework: pattern.framework,
symbolKind: symbolData.metadata.symbolKind,
platforms: this.formatPlatforms(symbolData.metadata.platforms || []),
url: `https://developer.apple.com/${symbolPath}`,
relevanceScore,
type: 'technical'
});
}
}
catch {
// Symbol doesn't exist, continue to next pattern
continue;
}
}
return results;
}
/**
* Guess the most likely framework for a symbol
*/
guessFramework(symbol) {
const symbolLower = symbol.toLowerCase();
if (symbolLower.startsWith('ui'))
return 'UIKit';
if (symbolLower.startsWith('ns'))
return 'AppKit';
if (symbolLower.startsWith('ca'))
return 'QuartzCore';
if (symbolLower.startsWith('cl'))
return 'CoreLocation';
if (symbolLower.startsWith('av'))
return 'AVFoundation';
// Common SwiftUI symbols
if (['button', 'text', 'image', 'list', 'vstack', 'hstack', 'zstack', 'navigationview', 'tabview'].includes(symbolLower)) {
return 'SwiftUI';
}
// Default to UIKit for most UI-related queries
if (symbolLower.includes('view') || symbolLower.includes('button') || symbolLower.includes('label')) {
return 'UIKit';
}
return 'Foundation';
}
/**
* Calculate relevance score for direct symbol matches
*/
calculateDirectSymbolRelevance(symbolTitle, query) {
const titleLower = symbolTitle.toLowerCase();
const queryLower = query.toLowerCase();
if (titleLower === queryLower)
return 1.0;
if (titleLower.includes(queryLower))
return 0.9;
if (queryLower.includes(titleLower))
return 0.8;
return 0.7;
}
/**
* Prioritize frameworks based on query hints and common usage
*/
prioritizeFrameworks(frameworks, query) {
const queryLower = query.toLowerCase();
// Create priority mapping
const priorities = new Map();
frameworks.forEach(framework => {
let priority = 0;
const name = framework.title.toLowerCase();
// Boost based on query hints
if (queryLower.includes('ui') || queryLower.includes('button') || queryLower.includes('view')) {
if (name === 'uikit')
priority += 100;
if (name === 'swiftui')
priority += 90;
}
if (queryLower.includes('swift') || queryLower.includes('view') || queryLower.includes('stack')) {
if (name === 'swiftui')
priority += 100;
if (name === 'uikit')
priority += 80;
}
if (queryLower.includes('ns') || queryLower.includes('appkit')) {
if (name === 'appkit')
priority += 100;
}
// General framework popularity
if (name === 'swiftui')
priority += 50;
if (name === 'uikit')
priority += 45;
if (name === 'foundation')
priority += 40;
if (name === 'appkit')
priority += 35;
if (name === 'core graphics')
priority += 30;
priorities.set(framework.title, priority);
});
return frameworks.sort((a, b) => (priorities.get(b.title) || 0) - (priorities.get(a.title) || 0));
}
/**
* Search within a specific framework
*/
async searchFramework(frameworkName, query, options = {}) {
const { maxResults = 20, includeRelevanceScore = true } = options;
const results = [];
try {
const framework = await this.getFramework(frameworkName);
const searchPattern = this.createSearchPattern(query);
// Search through all references (including those in topic sections)
Object.entries(framework.references || {}).forEach(([_id, ref]) => {
if (results.length >= maxResults)
return;
if (this.matchesSearch(ref, searchPattern, options)) {
const relevanceScore = includeRelevanceScore
? this.calculateRelevanceScore(ref, query)
: 1.0;
results.push({
title: ref.title,
description: this.extractText(ref.abstract || []),
path: ref.url,
framework: frameworkName,
symbolKind: ref.kind,
platforms: this.formatPlatforms(ref.platforms || framework.metadata?.platforms),
url: `https://developer.apple.com${ref.url}`,
relevanceScore,
type: 'technical'
});
}
});
// Also search through topic section identifiers for additional symbols
if (framework.topicSections) {
for (const section of framework.topicSections) {
if (section.identifiers) {
for (const identifier of section.identifiers) {
if (results.length >= maxResults)
break;
const ref = framework.references?.[identifier];
if (ref && this.matchesSearch(ref, searchPattern, options)) {
const relevanceScore = includeRelevanceScore
? this.calculateRelevanceScore(ref, query)
: 1.0;
results.push({
title: ref.title,
description: this.extractText(ref.abstract || []),
path: ref.url,
framework: frameworkName,
symbolKind: ref.kind,
platforms: this.formatPlatforms(ref.platforms || framework.metadata?.platforms),
url: `https://developer.apple.com${ref.url}`,
relevanceScore,
type: 'technical'
});
}
}
}
}
}
return results.sort((a, b) => (b.relevanceScore || 0) - (a.relevanceScore || 0));
}
catch (error) {
throw new Error(`Framework search failed for ${frameworkName}: ${error}`);
}
}
/**
* Get list of available frameworks
*/
async getFrameworkList() {
const technologies = await this.getTechnologies();
const frameworks = Object.values(technologies).filter(tech => tech.kind === 'symbol' && tech.role === 'collection');
return frameworks.map(framework => ({
name: framework.title,
description: this.extractText(framework.abstract),
platforms: [] // Platform info would need to be fetched per framework
}));
}
/**
* Check if a path exists in Apple's documentation
*/
async pathExists(path) {
try {
await this.getSymbol(path);
return true;
}
catch {
return false;
}
}
/**
* Get framework information by name
*/
async getFrameworkInfo(frameworkName) {
const framework = await this.getFramework(frameworkName);
return {
name: framework.metadata.title,
description: this.extractText(framework.abstract),
platforms: this.formatPlatformList(framework.metadata.platforms),
topicSections: framework.topicSections.map(section => section.title),
url: `https://developer.apple.com/documentation/${frameworkName.toLowerCase()}`
};
}
// Helper methods
createSearchPattern(query) {
// Convert wildcard pattern to regex
const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const pattern = escaped.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
return new RegExp(pattern, 'i');
}
matchesSearch(ref, pattern, options) {
if (!ref.title)
return false;
// Title match
if (!pattern.test(ref.title))
return false;
// Symbol type filter
if (options.symbolType && ref.kind !== options.symbolType)
return false;
// Platform filter
if (options.platform && ref.platforms) {
const hasPlat = ref.platforms.some((p) => p.name?.toLowerCase().includes(options.platform.toLowerCase()));
if (!hasPlat)
return false;
}
return true;
}
calculateRelevanceScore(ref, query) {
const title = ref.title?.toLowerCase() || '';
const queryLower = query.replace(/\*/g, '').toLowerCase();
if (title === queryLower)
return 1.0; // Exact match
if (title.startsWith(queryLower))
return 0.9; // Prefix match
if (title.includes(queryLower))
return 0.7; // Contains match
return 0.5; // Pattern match
}
extractFrameworkFromPath(path) {
const match = path.match(/^(?:documentation\/)?([^/]+)/);
return match ? match[1] : 'Unknown';
}
extractCodeExamples(symbolData) {
const examples = [];
// Extract from primary content sections
if (symbolData.primaryContentSections) {
for (const section of symbolData.primaryContentSections) {
if (section.kind === 'code' && section.code) {
examples.push(section.code);
}
}
}
return examples;
}
extractRelatedSymbols(symbolData) {
const related = [];
// Extract from topic sections
if (symbolData.topicSections) {
for (const section of symbolData.topicSections) {
if (section.identifiers) {
for (const identifier of section.identifiers.slice(0, 3)) {
const ref = symbolData.references?.[identifier];
if (ref && ref.title) {
related.push(ref.title);
}
}
}
}
}
return related.slice(0, 5); // Limit to 5 related symbols
}
generateAPIReference(symbolData) {
let reference = `# ${symbolData.metadata.title}\n\n`;
if (symbolData.metadata.symbolKind) {
reference += `**Type:** ${symbolData.metadata.symbolKind}\n`;
}
if (symbolData.metadata.platforms) {
reference += `**Platforms:** ${this.formatPlatforms(symbolData.metadata.platforms)}\n\n`;
}
if (symbolData.abstract) {
reference += `## Overview\n${this.extractText(symbolData.abstract)}\n\n`;
}
return reference;
}
extractText(abstract) {
return abstract?.map(item => item.text).join('') || '';
}
formatPlatforms(platforms) {
if (!platforms || platforms.length === 0)
return 'All platforms';
return platforms
.map(p => `${p.name} ${p.introducedAt}+${p.beta ? ' (Beta)' : ''}`)
.join(', ');
}
formatPlatformList(platforms) {
if (!platforms || platforms.length === 0)
return [];
return platforms.map(p => p.name || '').filter(Boolean);
}
}
//# sourceMappingURL=apple-content-api-client.service.js.map