add_docset
Install a new documentation set for API reference by providing a local .docset file or direct download URL, enabling AI assistants to access project-specific API documentation.
Instructions
Install a new documentation set (docset) for API reference. Supports both local .docset files and direct URLs.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| source | Yes | Path to local .docset file/directory or URL to download. Examples: "/Downloads/Swift.docset", "https://example.com/React.docset.tgz" |
Implementation Reference
- src/index.js:308-321 (schema)Tool schema/input definition for 'add_docset' - defines name, description, and inputSchema with a required 'source' property
{ name: 'add_docset', description: 'Install a new documentation set (docset) for API reference. Supports both local .docset files and direct URLs.', inputSchema: { type: 'object', properties: { source: { type: 'string', description: 'Path to local .docset file/directory or URL to download. Examples: "/Downloads/Swift.docset", "https://example.com/React.docset.tgz"' } }, required: ['source'] } }, - src/index.js:498-517 (handler)Tool handler for 'add_docset' - extracts 'source' from args, calls this.docsetService.addDocset(source), then registers it in multiDocsetDb, returns success message with docset info
case 'add_docset': const source = args?.source; if (!source) { throw new Error('source parameter is required'); } try { const docsetInfo = await this.docsetService.addDocset(source); // Add to the database for searching this.multiDocsetDb.addDocset(docsetInfo); return { content: [{ type: 'text', text: `✅ Successfully installed docset!\n\n**Name:** ${docsetInfo.name}\n**ID:** ${docsetInfo.id}\n**Path:** ${docsetInfo.path}\n\nThe docset is now available for searching with \`search_documentation\` and exploring with \`explore_api\`.` }] }; } catch (error) { throw new Error(`Failed to add docset: ${error.message}`); } - src/services/docset/index.js:59-81 (handler)DocsetService.addDocset() - determines if source is URL or local path, generates unique docsetId via MD5 hash, and delegates to addDocsetFromUrl() or addDocsetFromLocal()
async addDocset(source) { // Determine if source is URL or local path const isUrl = source.startsWith('http://') || source.startsWith('https://'); const isLocalPath = await this.isLocalPath(source); if (!isUrl && !isLocalPath) { throw new Error(`Invalid source: ${source}. Must be a valid URL or local file path.`); } // Generate unique ID for the docset const docsetId = crypto.createHash('md5').update(source).digest('hex').substring(0, 8); // Check if already exists if (this.docsets.has(docsetId)) { throw new Error(`Docset from ${source} is already installed`); } if (isUrl) { return await this.addDocsetFromUrl(source, docsetId); } else { return await this.addDocsetFromLocal(source, docsetId); } } - src/services/docset/index.js:92-168 (helper)DocsetService.addDocsetFromLocal() - handles local .docset directories and archive files (.tgz, .tar.gz, .zip), extracts/ copies them, reads Info.plist metadata, and returns docsetInfo
async addDocsetFromLocal(localPath, docsetId) { try { const stats = await fs.stat(localPath); const extractPath = path.join(this.storagePath, docsetId); await fs.ensureDir(extractPath); let docsetPath; if (stats.isDirectory() && localPath.endsWith('.docset')) { // Direct docset directory - copy it docsetPath = path.join(extractPath, path.basename(localPath)); await fs.copy(localPath, docsetPath); } else if (stats.isFile()) { // Handle different archive formats if (localPath.endsWith('.tgz') || localPath.endsWith('.tar.gz')) { // Tar archive - extract it await tar.extract({ file: localPath, cwd: extractPath, strip: 0 }); } else if (localPath.endsWith('.zip')) { // ZIP archive - extract it const zip = new AdmZip(localPath); zip.extractAllTo(extractPath, true); } else { throw new Error('Local file must be a .tgz, .tar.gz, or .zip archive'); } // Find the .docset directory const files = await fs.readdir(extractPath); const docsetDir = files.find(f => f.endsWith('.docset')); if (!docsetDir) { throw new Error('No .docset directory found in the archive'); } docsetPath = path.join(extractPath, docsetDir); } else { throw new Error('Local path must be a .docset directory or archive file (.tgz, .tar.gz, .zip)'); } // Read docset metadata const metadata = await this.readDocsetMetadata(docsetPath); // Get docset size const size = await this.getDirectorySize(docsetPath); // Create docset info const docsetInfo = { id: docsetId, name: metadata.CFBundleName || path.basename(docsetPath).replace('.docset', ''), source: localPath, path: docsetPath, version: metadata.CFBundleVersion, platform: metadata.DocSetPlatformFamily, downloadedAt: new Date(), size }; // Save to metadata this.docsets.set(docsetId, docsetInfo); await this.saveMetadata(); return docsetInfo; } catch (error) { // Clean up on failure try { const extractPath = path.join(this.storagePath, docsetId); await fs.remove(extractPath); } catch (cleanupError) { // Ignore cleanup errors } throw error; } } - src/services/docset/index.js:170-275 (helper)DocsetService.addDocsetFromUrl() - downloads docset from URL, extracts archive, reads Info.plist metadata, saves to storage, returns docsetInfo with progress tracking
async addDocsetFromUrl(url, docsetId) { const progress = { docsetId, url, totalBytes: 0, downloadedBytes: 0, percentage: 0, status: 'downloading' }; this.downloadProgress.set(docsetId, progress); try { // Determine file extension from URL const urlLower = url.toLowerCase(); let fileExt = '.tgz'; if (urlLower.endsWith('.zip')) { fileExt = '.zip'; } else if (urlLower.endsWith('.tar.gz')) { fileExt = '.tar.gz'; } // Download the docset const downloadPath = path.join(this.storagePath, `${docsetId}${fileExt}`); await this.downloadFile(url, downloadPath, docsetId); // Update progress progress.status = 'extracting'; // Extract the docset const extractPath = path.join(this.storagePath, docsetId); await fs.ensureDir(extractPath); if (fileExt === '.zip') { // Extract ZIP file const zip = new AdmZip(downloadPath); zip.extractAllTo(extractPath, true); } else { // Extract tar archive await tar.extract({ file: downloadPath, cwd: extractPath, strip: 0 }); } // Clean up downloaded file await fs.unlink(downloadPath); // Find the .docset directory const files = await fs.readdir(extractPath); const docsetDir = files.find(f => f.endsWith('.docset')); if (!docsetDir) { throw new Error('No .docset directory found in the downloaded archive'); } const docsetPath = path.join(extractPath, docsetDir); // Read docset metadata const metadata = await this.readDocsetMetadata(docsetPath); // Get docset size const size = await this.getDirectorySize(docsetPath); // Create docset info const docsetInfo = { id: docsetId, name: metadata.CFBundleName || docsetDir.replace('.docset', ''), source: url, path: docsetPath, version: metadata.CFBundleVersion, platform: metadata.DocSetPlatformFamily, downloadedAt: new Date(), size }; // Save to metadata this.docsets.set(docsetId, docsetInfo); await this.saveMetadata(); // Update progress progress.status = 'completed'; progress.percentage = 100; return docsetInfo; } catch (error) { progress.status = 'failed'; progress.error = error.message; // Clean up any partial downloads try { const extractPath = path.join(this.storagePath, docsetId); await fs.remove(extractPath); } catch (cleanupError) { // Ignore cleanup errors } throw error; } finally { // Clean up progress after a delay setTimeout(() => { this.downloadProgress.delete(docsetId); }, 5000); } }