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
JavaScript
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';
}