UNPKG

hatch-slidev-builder-mcp

Version:

A comprehensive MCP server for creating Slidev presentations with component library, interactive elements, and team collaboration features

160 lines (159 loc) • 7.39 kB
import * as fs from 'fs-extra'; import * as path from 'path'; export async function listComponents(args = {}) { const { category, scope = 'all', search, author } = args; try { // Read component registry const registryPath = path.join(__dirname, '..', 'components', 'registry.json'); let registry; try { const registryContent = await fs.readFile(registryPath, 'utf-8'); registry = JSON.parse(registryContent); } catch (error) { // Create empty registry if it doesn't exist registry = { components: {} }; await fs.ensureDir(path.dirname(registryPath)); await fs.writeFile(registryPath, JSON.stringify(registry, null, 2)); } // Filter components based on criteria let components = Object.entries(registry.components); if (scope !== 'all') { components = components.filter(([name]) => name.startsWith(`${scope}/`)); } if (category) { components = components.filter(([_, config]) => config.category === category); } if (author) { components = components.filter(([_, config]) => config.author.toLowerCase().includes(author.toLowerCase())); } if (search) { components = components.filter(([name, config]) => name.toLowerCase().includes(search.toLowerCase()) || config.description.toLowerCase().includes(search.toLowerCase()) || config.tags.some((tag) => tag.toLowerCase().includes(search.toLowerCase()))); } // Sort by popularity (downloads) and name components.sort(([nameA, configA], [nameB, configB]) => { const downloadsA = configA.downloads || 0; const downloadsB = configB.downloads || 0; if (downloadsA !== downloadsB) { return downloadsB - downloadsA; // Sort by downloads desc } return nameA.localeCompare(nameB); // Then by name }); // Format output if (components.length === 0) { return { content: [ { type: 'text', text: `šŸ“­ No components found matching your criteria.\n\n` + `šŸ” Search criteria:\n` + `${category ? ` • Category: ${category}\n` : ''}` + `${scope !== 'all' ? ` • Scope: ${scope}\n` : ''}` + `${search ? ` • Search: "${search}"\n` : ''}` + `${author ? ` • Author: "${author}"\n` : ''}` + `\nšŸ’” Try:\n` + ` • Broadening your search criteria\n` + ` • Using list_components without filters\n` + ` • Creating a new component with create_component` } ] }; } // Generate component list const componentList = components.map(([name, config]) => { const scope = name.split('/')[0]; const componentName = name.split('/')[1]; const downloads = config.downloads || 0; const rating = config.rating || 0; const version = config.version || '1.0.0'; return `## šŸ“¦ **${name}** (v${version})\n` + `${config.description}\n\n` + `**Details:**\n` + `- šŸ‘¤ **Author:** ${config.author}\n` + `- šŸ“‚ **Category:** ${config.category}\n` + `- šŸ·ļø **Tags:** ${config.tags.join(', ')}\n` + `- šŸ“Š **Downloads:** ${downloads.toLocaleString()}\n` + `${rating > 0 ? `- ⭐ **Rating:** ${rating}/5\n` : ''}` + `\n**Parameters:**\n` + `${Object.entries(config.parameters || {}).map(([paramName, paramConfig]) => `- \`${paramName}\` (${paramConfig.type}${paramConfig.required ? ', required' : ''}): ${paramConfig.description || 'No description'}`).join('\n')}\n` + `\n**Usage:**\n` + `\`\`\`javascript\n` + `await mcp.callTool('add_component', {\n` + ` componentName: '${name}',\n` + ` parameters: {\n` + `${Object.entries(config.parameters || {}).map(([paramName, paramConfig]) => ` ${paramName}: ${getExampleValue(paramConfig)}`).join(',\n')}\n` + ` }\n` + `});\n` + `\`\`\`\n`; }).join('\n---\n\n'); const summary = `šŸ“‹ **Component Library** (${components.length} component${components.length !== 1 ? 's' : ''} found)\n\n` + `šŸ” **Filters Applied:**\n` + `${category ? ` • Category: ${category}\n` : ''}` + `${scope !== 'all' ? ` • Scope: ${scope}\n` : ''}` + `${search ? ` • Search: "${search}"\n` : ''}` + `${author ? ` • Author: "${author}"\n` : ''}` + `\nšŸ“Š **Categories Available:**\n` + `${getAvailableCategories(registry.components)}\n` + `\nšŸ·ļø **Scopes Available:**\n` + `${getAvailableScopes(registry.components)}\n\n` + `---\n\n`; return { content: [ { type: 'text', text: summary + componentList + `\n---\n\nšŸ’” **Quick Commands:**\n` + `- Install: \`install_component <component-name>\`\n` + `- Use: \`add_component --name=<component-name>\`\n` + `- Create: \`create_component --name=<new-name>\`\n` + `- Search: \`list_components --search="<keyword>"\`` } ] }; } catch (error) { throw new Error(`Failed to list components: ${error instanceof Error ? error.message : String(error)}`); } } function getExampleValue(paramConfig) { const { type, default: defaultValue, options } = paramConfig; if (defaultValue !== undefined) { return JSON.stringify(defaultValue); } switch (type) { case 'string': return options ? `"${options[0]}"` : '"example"'; case 'number': return '42'; case 'boolean': return 'true'; case 'enum': return options ? `"${options[0]}"` : '"option1"'; case 'array': return '[1, 2, 3]'; case 'object': return '{}'; default: return '"value"'; } } function getAvailableCategories(components) { const categories = new Set(); Object.values(components).forEach((config) => { if (config.category) categories.add(config.category); }); const categoryList = Array.from(categories).sort(); return categoryList.map(cat => ` • ${cat}`).join('\n') || ' • No categories available'; } function getAvailableScopes(components) { const scopes = new Set(); Object.keys(components).forEach(name => { const scope = name.split('/')[0]; scopes.add(scope); }); const scopeList = Array.from(scopes).sort(); return scopeList.map(scope => ` • ${scope}`).join('\n') || ' • No scopes available'; }