templui-mcp-server
Version:
A Model Context Protocol (MCP) server for TemplUI components, providing AI assistants with access to component source code, documentation, demos, and metadata.
278 lines • 11 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { logError, logInfo, logDebug } from './logger.js';
export class DocumentationParser {
docsPath;
constructor(docsPath = './samples/templui-site-doc') {
this.docsPath = docsPath;
}
/**
* Get documentation for a specific component
*/
async getComponentDocs(componentName) {
try {
const docFileName = `templui.io_docs_components_${componentName.toLowerCase()}.md`;
const docPath = path.join(this.docsPath, docFileName);
if (!fs.existsSync(docPath)) {
logDebug(`Documentation file not found: ${docPath}`);
return null;
}
const content = fs.readFileSync(docPath, 'utf-8');
return this.parseMarkdownContent(content, componentName);
}
catch (error) {
logError(`Failed to read documentation for ${componentName}`, error);
return null;
}
}
/**
* Get list of all documented components
*/
async getAvailableComponentDocs() {
try {
if (!fs.existsSync(this.docsPath)) {
logError(`Documentation directory not found: ${this.docsPath}`, new Error('Directory not found'));
return [];
}
const files = fs.readdirSync(this.docsPath);
const componentFiles = files.filter(file => file.startsWith('templui.io_docs_components_') &&
file.endsWith('.md') &&
!file.includes('templui.io_docs_components.md') // Exclude the index file
);
const components = componentFiles.map(file => {
const componentName = file
.replace('templui.io_docs_components_', '')
.replace('.md', '')
.replace(/-/g, ''); // Remove hyphens for consistency
return componentName;
});
logInfo(`Found documentation for ${components.length} components`);
return components;
}
catch (error) {
logError('Failed to read documentation directory', error);
return [];
}
}
/**
* Parse markdown content into structured documentation
*/
parseMarkdownContent(content, componentName) {
const lines = content.split('\n');
let title = '';
let description = '';
let installation = '';
const examples = [];
let apiTable = '';
let usage = '';
let currentSection = '';
let currentCodeBlock = '';
let insideCodeBlock = false;
let insideApiTable = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmedLine = line.trim();
// Extract title from frontmatter or main heading
if (trimmedLine.startsWith('title:')) {
title = trimmedLine.replace('title:', '').replace(/"/g, '').trim();
continue;
}
// Main heading
if (trimmedLine.startsWith('# ') && !title) {
title = trimmedLine.replace('# ', '').trim();
continue;
}
// Component description (usually the first paragraph after title)
if (!description && title && trimmedLine && !trimmedLine.startsWith('#') && !trimmedLine.startsWith('---') && !trimmedLine.startsWith('[')) {
description = trimmedLine;
continue;
}
// Section headers
if (trimmedLine.startsWith('## ')) {
currentSection = trimmedLine.replace('## ', '').toLowerCase();
continue;
}
// Installation section
if (currentSection === 'installation' && trimmedLine.startsWith('```')) {
if (!insideCodeBlock) {
insideCodeBlock = true;
currentCodeBlock = '';
}
else {
insideCodeBlock = false;
if (currentCodeBlock.trim().startsWith('templui add')) {
installation = currentCodeBlock.trim();
}
currentCodeBlock = '';
}
continue;
}
// Code blocks for examples
if (trimmedLine.startsWith('```')) {
if (!insideCodeBlock) {
insideCodeBlock = true;
currentCodeBlock = '';
}
else {
insideCodeBlock = false;
if (currentCodeBlock.trim() && currentSection === 'examples') {
examples.push(this.formatCodeExample(currentCodeBlock.trim(), componentName));
}
currentCodeBlock = '';
}
continue;
}
// Collect code block content
if (insideCodeBlock) {
currentCodeBlock += line + '\n';
continue;
}
// API table detection (simple heuristic)
if (trimmedLine.includes('|') && (trimmedLine.includes('Prop') || trimmedLine.includes('Type') || trimmedLine.includes('Default'))) {
insideApiTable = true;
apiTable += line + '\n';
continue;
}
if (insideApiTable && trimmedLine.includes('|')) {
apiTable += line + '\n';
continue;
}
else if (insideApiTable) {
insideApiTable = false;
}
// Usage patterns
if (currentSection === 'usage' || currentSection === 'examples') {
if (trimmedLine && !trimmedLine.startsWith('```')) {
usage += line + '\n';
}
}
}
return {
name: componentName,
title: title || componentName,
description: description || `${componentName} component for TemplUI`,
installation: installation || `templui add ${componentName.toLowerCase()}`,
examples,
apiTable: apiTable.trim() || undefined,
usage: usage.trim() || undefined,
rawContent: content
};
}
/**
* Format code examples with helpful comments
*/
formatCodeExample(code, componentName) {
const lines = code.split('\n');
const formattedLines = [];
// Add header comment
formattedLines.push(`// Example usage of ${componentName} component`);
formattedLines.push('// Copy this code to your .templ file');
formattedLines.push('');
let insidePackageDeclaration = false;
let insideImports = false;
for (const line of lines) {
const trimmedLine = line.trim();
// Package declaration
if (trimmedLine.startsWith('package ')) {
formattedLines.push('// Package declaration for the showcase');
insidePackageDeclaration = true;
}
// Import statements
if (trimmedLine.startsWith('import ')) {
if (!insideImports) {
formattedLines.push('');
formattedLines.push('// Import TemplUI components');
insideImports = true;
}
}
// Template function
if (trimmedLine.startsWith('templ ')) {
if (insideImports) {
formattedLines.push('');
formattedLines.push('// Template function - replace with your own function name');
insideImports = false;
}
}
// Component usage
if (trimmedLine.includes(`@${componentName.toLowerCase()}.`)) {
formattedLines.push(` // ${componentName} component with configuration`);
}
formattedLines.push(line);
if (insidePackageDeclaration && trimmedLine) {
formattedLines.push('');
insidePackageDeclaration = false;
}
}
return formattedLines.join('\n');
}
/**
* Extract metadata from component documentation
*/
async getComponentMetadata(componentName) {
const docs = await this.getComponentDocs(componentName);
if (!docs) {
return null;
}
const metadata = {
name: docs.name,
title: docs.title,
description: docs.description,
installation: docs.installation,
hasExamples: docs.examples.length > 0,
hasApiTable: !!docs.apiTable,
exampleCount: docs.examples.length,
category: this.inferCategory(docs.rawContent),
features: this.extractFeatures(docs.rawContent)
};
return metadata;
}
/**
* Infer component category from documentation content
*/
inferCategory(content) {
const lowerContent = content.toLowerCase();
if (lowerContent.includes('form') || lowerContent.includes('input') || lowerContent.includes('button')) {
return 'form';
}
if (lowerContent.includes('navigation') || lowerContent.includes('menu') || lowerContent.includes('breadcrumb')) {
return 'navigation';
}
if (lowerContent.includes('overlay') || lowerContent.includes('modal') || lowerContent.includes('popup')) {
return 'overlay';
}
if (lowerContent.includes('feedback') || lowerContent.includes('alert') || lowerContent.includes('toast')) {
return 'feedback';
}
if (lowerContent.includes('layout') || lowerContent.includes('container') || lowerContent.includes('grid')) {
return 'layout';
}
return 'display';
}
/**
* Extract key features from documentation
*/
extractFeatures(content) {
const features = [];
const lowerContent = content.toLowerCase();
if (lowerContent.includes('accessible') || lowerContent.includes('aria')) {
features.push('Accessibility support');
}
if (lowerContent.includes('responsive')) {
features.push('Responsive design');
}
if (lowerContent.includes('theme') || lowerContent.includes('dark mode')) {
features.push('Theme support');
}
if (lowerContent.includes('animation') || lowerContent.includes('transition')) {
features.push('Animated transitions');
}
if (lowerContent.includes('keyboard')) {
features.push('Keyboard navigation');
}
if (lowerContent.includes('validation')) {
features.push('Form validation');
}
return features;
}
}
//# sourceMappingURL=documentation.js.map