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

214 lines 10.5 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { parseSearchResults } from './tools/search-parser.js'; import { fetchAppleDocJson } from './tools/doc-fetcher.js'; import { handleListTechnologies } from './tools/list-technologies.js'; import { searchFrameworkSymbols } from './tools/search-framework-symbols.js'; import { toolDefinitions } from './tools/definitions.js'; import { handleToolCall } from './tools/handlers.js'; import { handleGetRelatedApis } from './tools/get-related-apis.js'; import { handleResolveReferencesBatch } from './tools/resolve-references-batch.js'; import { handleGetPlatformCompatibility } from './tools/get-platform-compatibility.js'; import { handleFindSimilarApis } from './tools/find-similar-apis.js'; import { handleGetDocumentationUpdates } from './tools/get-documentation-updates.js'; import { handleGetTechnologyOverviews } from './tools/get-technology-overviews.js'; import { handleGetSampleCode } from './tools/get-sample-code.js'; import { APPLE_URLS } from './utils/constants.js'; import { isValidAppleDeveloperUrl } from './utils/url-converter.js'; import { validateInput, ErrorType, createStandardErrorResponse, createToolErrorResponse } from './utils/error-handler.js'; import { httpClient } from './utils/http-client.js'; import { preloadPopularFrameworks } from './utils/preloader.js'; import { warmUpCaches, schedulePeriodicCacheRefresh } from './utils/cache-warmer.js'; import { logger } from './utils/logger.js'; import { API_LIMITS } from './utils/constants.js'; export default class AppleDeveloperDocsMCPServer { server; /** * Helper method to handle async operations with consistent error handling */ async handleAsyncOperation(operation, operationName) { try { const result = await operation(); return { content: [ { type: 'text', text: result, }, ], }; } catch (error) { // If error is already an AppError, use tool-specific suggestions if (error && typeof error === 'object' && 'type' in error) { return createToolErrorResponse(error, operationName); } return createStandardErrorResponse(error, operationName); } } constructor() { this.server = new Server({ name: 'apple-docs-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, }); this.setupTools(); this.setupErrorHandling(); } setupTools() { // const cacheStatsSchema = z.object({}); // 处理工具列表请求 this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: toolDefinitions, }; }); // 处理工具调用请求 this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { return await handleToolCall(name, args, this); } catch (error) { const appError = error instanceof Error ? { type: 'UNKNOWN', message: error.message, originalError: error } : { type: 'UNKNOWN', message: 'An unknown error occurred' }; logger.error(`Tool ${name} failed:`, appError); return { content: [ { type: 'text', text: `Error: ${appError.message}`, }, ], isError: true, }; } }); } async searchAppleDocs(query, type = 'all') { try { // 输入验证 const queryValidation = validateInput(query, 'Search query'); if (queryValidation) { return createToolErrorResponse(queryValidation, 'search_apple_docs'); } // 创建 Apple Developer Documentation 搜索 URL const searchUrl = `${APPLE_URLS.SEARCH}?q=${encodeURIComponent(query)}`; logger.info(`Searching Apple docs for: ${query}`); // 获取搜索结果页面 const html = await httpClient.getText(searchUrl); // 解析并返回搜索结果,传递type参数进行过滤 return parseSearchResults(html, query, searchUrl, type); } catch (error) { if (error && typeof error === 'object' && 'type' in error) { return createToolErrorResponse(error, 'search_apple_docs'); } return createStandardErrorResponse(error, 'search_apple_docs'); } } async getAppleDocContent(url, includeRelatedApis = false, includeReferences = false, includeSimilarApis = false, includePlatformAnalysis = false) { try { // 输入验证 const urlValidation = validateInput(url, 'URL'); if (urlValidation) { return createToolErrorResponse(urlValidation, 'get_apple_doc_content'); } // 验证是否为有效的Apple Developer URL if (!isValidAppleDeveloperUrl(url)) { return createToolErrorResponse({ type: ErrorType.INVALID_INPUT, message: 'URL must be from developer.apple.com', }, 'get_apple_doc_content'); } // fetchAppleDocJson 已经返回正确的MCP响应格式,直接返回 return await fetchAppleDocJson(url, { includeRelatedApis, includeReferences, includeSimilarApis, includePlatformAnalysis, }); } catch (error) { if (error && typeof error === 'object' && 'type' in error) { return createToolErrorResponse(error, 'get_apple_doc_content'); } return createStandardErrorResponse(error, 'get_apple_doc_content'); } } async listTechnologies(category, language, includeBeta = true, limit = API_LIMITS.DEFAULT_TECHNOLOGIES_LIMIT) { return this.handleAsyncOperation(() => handleListTechnologies(category, language, includeBeta, limit), 'listTechnologies'); } async searchFrameworkSymbols(framework, symbolType = 'all', namePattern, language = 'swift', limit = API_LIMITS.DEFAULT_FRAMEWORK_SYMBOLS_LIMIT) { return this.handleAsyncOperation(() => searchFrameworkSymbols(framework, symbolType, namePattern, language, limit), 'searchFrameworkSymbols'); } async getRelatedApis(apiUrl, includeInherited = true, includeConformance = true, includeSeeAlso = true) { return this.handleAsyncOperation(() => handleGetRelatedApis(apiUrl, includeInherited, includeConformance, includeSeeAlso), 'getRelatedApis'); } async resolveReferencesBatch(sourceUrl, maxReferences = API_LIMITS.DEFAULT_REFERENCES_LIMIT, filterByType = 'all') { return this.handleAsyncOperation(() => handleResolveReferencesBatch(sourceUrl, maxReferences, filterByType), 'resolveReferencesBatch'); } async getPlatformCompatibility(apiUrl, compareMode = 'single', includeRelated = false) { return this.handleAsyncOperation(() => handleGetPlatformCompatibility(apiUrl, compareMode, includeRelated), 'getPlatformCompatibility'); } async findSimilarApis(apiUrl, searchDepth = 'medium', filterByCategory, includeAlternatives = true) { return this.handleAsyncOperation(() => handleFindSimilarApis(apiUrl, searchDepth, filterByCategory, includeAlternatives), 'findSimilarApis'); } async getDocumentationUpdates(category = 'all', technology, year, searchQuery, includeBeta = true, limit = API_LIMITS.DEFAULT_DOCUMENTATION_UPDATES_LIMIT) { return this.handleAsyncOperation(() => handleGetDocumentationUpdates(category, technology, year, searchQuery, includeBeta, limit), 'getDocumentationUpdates'); } async getTechnologyOverviews(category, platform = 'all', searchQuery, includeSubcategories = true, limit = API_LIMITS.DEFAULT_TECHNOLOGY_OVERVIEWS_LIMIT) { return this.handleAsyncOperation(() => handleGetTechnologyOverviews(category, platform, searchQuery, includeSubcategories, limit), 'getTechnologyOverviews'); } async getSampleCode(framework, beta = 'include', searchQuery, limit = API_LIMITS.DEFAULT_SAMPLE_CODE_LIMIT) { return this.handleAsyncOperation(() => handleGetSampleCode(framework, beta, searchQuery, limit), 'getSampleCode'); } setupErrorHandling() { // 处理 SIGINT 以优雅关闭服务器 process.on('SIGINT', () => { process.exit(0); }); process.on('SIGTERM', () => { process.exit(0); }); process.on('unhandledRejection', (reason) => { logger.error('Unhandled Rejection, reason:', reason); process.exit(1); }); process.on('uncaughtException', (error) => { logger.error('Uncaught Exception:', error); process.exit(1); }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); logger.info('Apple Developer Docs MCP server running on stdio'); logger.info('Cache system initialized with TTL: API(30m), Index(1h), Technologies(2h)'); logger.info('Note: Search results are not cached to ensure real-time accuracy'); // Start framework preloading and cache warming in background Promise.all([ preloadPopularFrameworks(), warmUpCaches(), ]).catch(error => { logger.error('Background initialization failed:', error); }); // Schedule periodic cache refresh (every 30 minutes) schedulePeriodicCacheRefresh(); } } // 运行服务器 (仅在非测试环境中) if (process.env.NODE_ENV !== 'test') { const server = new AppleDeveloperDocsMCPServer(); void server.run().catch((error) => { logger.error('Fatal error in main():', error); process.exit(1); }); } //# sourceMappingURL=index.js.map