apple-developer-docs-mcp
Version:
An MCP server that fetches the right data from Apple's developer documentation site
469 lines • 19.3 kB
JavaScript
import * as cheerio from 'cheerio';
// Global cache integration instance (will be set by the main server)
let cacheIntegration = null;
/**
* Set the global cache integration instance
*/
export function setCacheIntegration(integration) {
cacheIntegration = integration;
}
/**
* Create a resource link from Apple documentation reference
*/
function createResourceLink(reference, identifier) {
let uri = '';
if (reference?.url) {
uri = reference.url.startsWith('http') ? reference.url : `https://developer.apple.com${reference.url}`;
}
else if (identifier.startsWith('http')) {
uri = identifier;
}
else {
// Fallback to identifier-based URL
uri = `https://developer.apple.com/documentation/${identifier.replace(/^doc:\/\//, '').replace(/\./g, '/')}`;
}
const name = reference?.title || extractTitleFromUrl(uri) || identifier.split('/').pop() || identifier;
const description = reference?.abstract ? processInlineContent(reference.abstract) : undefined;
return {
type: "resource_link",
uri,
name,
description,
mimeType: "text/html"
};
}
/**
* Create a resource link for sample code downloads
*/
function createSampleCodeResourceLink(sampleCodeData) {
if (!sampleCodeData?.action?.identifier) {
return null;
}
const sampleId = sampleCodeData.action.identifier;
const sampleUrl = `https://docs-assets.developer.apple.com/published/${sampleId}`;
const name = sampleCodeData.title || 'Sample Code';
return {
type: "resource_link",
uri: sampleUrl,
name,
description: "Downloadable sample code from Apple Developer Documentation",
mimeType: "application/zip"
};
}
/**
* Create resource links from topic identifiers
*/
function createTopicResourceLinks(identifiers, references = {}) {
if (!identifiers || !Array.isArray(identifiers)) {
return [];
}
return identifiers.map(identifier => {
const reference = references[identifier];
return createResourceLink(reference, identifier);
}).filter(Boolean);
}
/**
* Create resource links from HTML topic elements
*/
function createHtmlTopicResourceLinks($, topicElements) {
const resourceLinks = [];
topicElements.each((_j, topicItem) => {
const topicLink = $(topicItem).find('a');
const topicText = topicLink.text().trim();
const topicUrl = topicLink.attr('href');
if (topicText && topicUrl) {
const fullUrl = topicUrl.startsWith('http') ?
topicUrl : `https://developer.apple.com${topicUrl}`;
resourceLinks.push({
type: "resource_link",
uri: fullUrl,
name: topicText,
description: "Related Apple Developer Documentation",
mimeType: "text/html"
});
}
});
return resourceLinks;
}
/**
* Cache-aware wrapper for formatJsonDocumentation
*/
export async function formatJsonDocumentationCached(jsonData, url, options) {
if (cacheIntegration) {
return cacheIntegration.cacheAwareFormat(url, () => {
const result = formatJsonDocumentation(jsonData, url);
// Convert ContentBlock array to single text block for caching
const textBlocks = result.content.filter(block => block.type === 'text').map(block => block.text || '');
const combinedText = textBlocks.join('\n\n');
return {
content: [{
type: "text",
text: combinedText,
}],
};
}, options);
}
// Fallback to non-cached version - return full ContentBlock array
return {
content: formatJsonDocumentation(jsonData, url),
fromCache: false,
cacheKey: ''
};
}
/**
* Cache-aware wrapper for formatHtmlDocumentation
*/
export async function formatHtmlDocumentationCached(html, url, options) {
if (cacheIntegration) {
return cacheIntegration.cacheAwareFormat(url, () => {
const result = formatHtmlDocumentation(html, url);
// Convert ContentBlock array to single text block for caching
const textBlocks = result.content.filter(block => block.type === 'text').map(block => block.text || '');
const combinedText = textBlocks.join('\n\n');
return {
content: [{
type: "text",
text: combinedText,
}],
};
}, options);
}
// Fallback to non-cached version - return full ContentBlock array
return {
content: formatHtmlDocumentation(html, url),
fromCache: false,
cacheKey: ''
};
}
/**
* Format JSON documentation from Apple Developer Documentation
*/
export function formatJsonDocumentation(jsonData, url) {
try {
// Extract the key information from the JSON structure
const title = jsonData.title || jsonData.metadata?.title || 'Untitled Documentation';
// Initialize content blocks with main content
const contentBlocks = [];
// Add main documentation text
let markdownContent = `# ${title}\n\n`;
markdownContent += `**Source:** [${url}](${url})\n\n`;
// Add abstract/introduction if available
if (jsonData.abstract && jsonData.abstract.length > 0) {
const abstractText = processInlineContent(jsonData.abstract);
markdownContent += `## Overview\n\n${abstractText}\n\n`;
}
// Add declaration if available
if (jsonData.primaryContentSections) {
const declarationSection = jsonData.primaryContentSections.find((section) => section.kind === 'declarations');
if (declarationSection && declarationSection.declarations) {
markdownContent += `## Declaration\n\n\`\`\`swift\n`;
declarationSection.declarations.forEach((declaration) => {
if (declaration.tokens) {
const declarationText = declaration.tokens
.map((token) => token.text || '')
.join('');
markdownContent += `${declarationText}\n`;
}
});
markdownContent += `\`\`\`\n\n`;
}
// Add discussion/description if available
const discussionSection = jsonData.primaryContentSections.find((section) => section.kind === 'content');
if (discussionSection && discussionSection.content) {
markdownContent += `## Description\n\n`;
markdownContent += processContentItems(discussionSection.content, jsonData.references);
}
// Add parameters if available
const parametersSection = jsonData.primaryContentSections.find((section) => section.kind === 'parameters');
if (parametersSection && parametersSection.parameters) {
markdownContent += `## Parameters\n\n`;
parametersSection.parameters.forEach((param) => {
markdownContent += `### \`${param.name}\`\n\n`;
if (param.content) {
markdownContent += processContentItems(param.content, jsonData.references);
}
});
}
// Add return value if available
const returnSection = jsonData.primaryContentSections.find((section) => section.kind === 'returnValue');
if (returnSection && returnSection.content) {
markdownContent += `## Return Value\n\n`;
markdownContent += processContentItems(returnSection.content, jsonData.references);
}
}
// Add platform availability information
if (jsonData.metadata && jsonData.metadata.platforms) {
markdownContent += `## Availability\n\n`;
jsonData.metadata.platforms.forEach((platform) => {
const betaStatus = platform.beta ? ' (Beta)' : '';
markdownContent += `- **${platform.name}${betaStatus}**: Introduced in ${platform.introducedAt}\n`;
});
markdownContent += `\n`;
}
// Add the main documentation content
contentBlocks.push({
type: "text",
text: markdownContent
});
// Add sample code as resource link if available
if (jsonData.sampleCodeDownload) {
const sampleCodeLink = createSampleCodeResourceLink(jsonData.sampleCodeDownload);
if (sampleCodeLink) {
contentBlocks.push(sampleCodeLink);
}
}
// Add topics/subtopics as resource links
if (jsonData.topicSections && jsonData.topicSections.length > 0) {
jsonData.topicSections.forEach((topic) => {
if (topic.identifiers && topic.identifiers.length > 0) {
const topicLinks = createTopicResourceLinks(topic.identifiers, jsonData.references);
contentBlocks.push(...topicLinks);
}
});
}
// Add related items as resource links
if (jsonData.relationshipsSections && jsonData.relationshipsSections.length > 0) {
jsonData.relationshipsSections.forEach((section) => {
if (section.identifiers && section.identifiers.length > 0) {
const relatedLinks = createTopicResourceLinks(section.identifiers, jsonData.references);
contentBlocks.push(...relatedLinks);
}
});
}
// Add see also section as resource links
if (jsonData.seeAlsoSections && jsonData.seeAlsoSections.length > 0) {
jsonData.seeAlsoSections.forEach((section) => {
if (section.identifiers && section.identifiers.length > 0) {
section.identifiers.forEach((identifier) => {
if (identifier.startsWith('http')) {
// External URL
contentBlocks.push({
type: "resource_link",
uri: identifier,
name: extractTitleFromUrl(identifier),
description: "External reference from Apple Developer Documentation",
mimeType: "text/html"
});
}
else {
const reference = jsonData.references && jsonData.references[identifier];
const resourceLink = createResourceLink(reference, identifier);
contentBlocks.push(resourceLink);
}
});
}
});
}
return {
content: contentBlocks,
};
}
catch (error) {
console.error('Error formatting JSON documentation:', error);
// Return a simplified version if parsing fails
return {
content: [
{
type: "text",
text: `# Documentation: ${url}\n\nUnable to parse the full documentation content. Please visit the original documentation page for complete information.`,
},
],
};
}
}
/**
* Process inline content from Apple Documentation JSON
*/
function processInlineContent(items) {
if (!items || !Array.isArray(items))
return '';
return items.map((item) => {
if (item.text)
return item.text;
if (item.inlineContent) {
return processInlineContent(item.inlineContent);
}
if (item.type === 'reference' && item.identifier) {
return `\`${item.identifier.split('/').pop()}\``;
}
return '';
}).join('');
}
/**
* Process content items from Apple Documentation JSON
*/
function processContentItems(items, references = {}) {
if (!items || !Array.isArray(items))
return '';
let result = '';
items.forEach((item) => {
if (item.type === 'paragraph' && item.inlineContent) {
result += `${processInlineContent(item.inlineContent)}\n\n`;
}
else if (item.type === 'heading') {
result += `### ${item.text}\n\n`;
}
else if (item.type === 'codeBlock') {
result += `\`\`\`${item.syntax || ''}\n${item.code || ''}\n\`\`\`\n\n`;
}
else if (item.type === 'unorderedList' && item.items) {
item.items.forEach((listItem) => {
if (listItem.content) {
result += `- ${processContentItems(listItem.content, references).trim()}\n`;
}
});
result += '\n';
}
else if (item.type === 'orderedList' && item.items) {
item.items.forEach((listItem, index) => {
if (listItem.content) {
result += `${index + 1}. ${processContentItems(listItem.content, references).trim()}\n`;
}
});
result += '\n';
}
else if (item.type === 'aside' && item.style && item.content) {
result += `> **${item.style.toUpperCase()}**: ${processContentItems(item.content, references).trim()}\n\n`;
}
else if (item.type === 'codeListing') {
if (item.syntax) {
result += `\`\`\`${item.syntax}\n`;
}
else {
result += '```\n';
}
if (item.fileLocation) {
result += `// ${item.fileLocation}\n`;
}
if (item.code) {
result += `${item.code}\n`;
}
result += '```\n\n';
}
else if (item.type === 'image' && item.identifier) {
const image = references && references[item.identifier];
if (image && image.variants && image.variants.length > 0) {
const imageUrl = image.variants[0].url;
const altText = image.alt || 'Image';
result += `\n\n`;
}
}
});
return result;
}
/**
* Extract a title from a URL for display purposes
*/
function extractTitleFromUrl(url) {
try {
const pathParts = new URL(url).pathname.split('/');
let lastPart = pathParts[pathParts.length - 1];
// Remove file extension if present
if (lastPart.includes('.')) {
lastPart = lastPart.split('.')[0];
}
// Format the title: replace hyphens with spaces and capitalize
return lastPart
.replace(/-/g, ' ')
.replace(/\b\w/g, c => c.toUpperCase());
}
catch {
return url;
}
}
/**
* Format HTML documentation from Apple Developer Documentation
*/
export function formatHtmlDocumentation(html, url) {
try {
const $ = cheerio.load(html);
// Extract the title
const title = $('h1').first().text().trim() || $('title').text().trim();
// Initialize content blocks
const contentBlocks = [];
// Initialize the main content
let markdownContent = `# ${title}\n\n`;
markdownContent += `**Source:** [${url}](${url})\n\n`;
// Extract main content
const mainContent = $('.documentation-main').first();
if (mainContent.length > 0) {
// Extract the description/overview
const description = mainContent.find('.description, .abstract, .content-section').first();
if (description.length > 0) {
markdownContent += `## Overview\n\n${description.text().trim()}\n\n`;
}
// Extract code examples
const codeBlocks = mainContent.find('pre code');
if (codeBlocks.length > 0) {
markdownContent += `## Code Examples\n\n`;
codeBlocks.each((_i, element) => {
const code = $(element).text().trim();
const language = $(element).attr('class') || '';
const lang = language.includes('swift') ? 'swift' :
language.includes('objective-c') ? 'objective-c' : '';
markdownContent += `\`\`\`${lang}\n${code}\n\`\`\`\n\n`;
});
}
// Add main documentation content
contentBlocks.push({
type: "text",
text: markdownContent
});
// Extract topics/subtopics as resource links
const topics = mainContent.find('.topics-section');
if (topics.length > 0) {
topics.each((_i, element) => {
const topicItems = $(element).find('.topic');
if (topicItems.length > 0) {
topicItems.each((_j, topicElement) => {
const topicLink = $(topicElement).find('a');
const topicText = topicLink.text().trim();
const topicUrl = topicLink.attr('href');
if (topicText && topicUrl) {
const fullUrl = topicUrl.startsWith('http') ?
topicUrl : `https://developer.apple.com${topicUrl}`;
contentBlocks.push({
type: "resource_link",
uri: fullUrl,
name: topicText,
title: topicText,
description: `Apple Developer Documentation: ${topicText}`,
mimeType: "text/html"
});
}
});
}
});
}
}
else {
// Fallback for when the main content container isn't found
// Remove scripts, styles, and navigation elements
$('script, style, nav, header, footer').remove();
// Extract the remaining text content
let content = $('body').text().trim();
content = content.replace(/\s+/g, ' ').substring(0, 6000);
markdownContent += `## Content\n\n${content}\n\n`;
contentBlocks.push({
type: "text",
text: markdownContent
});
}
return {
content: contentBlocks,
};
}
catch (error) {
console.error('Error formatting HTML documentation:', error);
// Return a simplified version if parsing fails
return {
content: [
{
type: "text",
text: `# Documentation: ${url}\n\nUnable to parse the full documentation content. Please visit the original documentation page for complete information.`,
},
],
};
}
}
//# sourceMappingURL=doc-parsers.js.map