UNPKG

@trimble-oss/modus-mcp-server

Version:

An MCP server providing information about Modus React form and UI components

244 lines 10.7 kB
import * as fs from 'fs'; import * as path from 'path'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; export class ComponentRegistry { constructor() { // Use paths relative to the package root when installed via NPM const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // From dist/modules/component-registry.js, go up two levels to package root this.basePath = path.resolve(__dirname, '../..'); this.componentsPath = path.join(this.basePath, 'knowledge-base', 'modus2_components.json'); this.reactKbPath = path.join(this.basePath, 'knowledge-base', 'modus2_react_KB.md'); this.iconsPath = path.join(this.basePath, 'knowledge-base', 'modus_icons.json'); // Debug prints for deployment troubleshooting console.error(`ComponentRegistry initialized with base_path: ${this.basePath}`); console.error(`Components path: ${this.componentsPath}`); console.error(`React KB path: ${this.reactKbPath}`); console.error(`Icons path: ${this.iconsPath}`); } async getAllComponents() { try { const data = await this.readJsonFile(this.componentsPath); return Object.keys(data); } catch (error) { console.error(`Error getting component list: ${error}`); return []; } } async getComponentPropertiesAndEvents(componentName) { try { const data = await this.readJsonFile(this.componentsPath); if (componentName in data) { const componentData = data[componentName]; return { properties: componentData.properties || [], events: componentData.events || [], methods: componentData.methods || [], description: componentData.description || '' }; } return { properties: [], events: [], methods: [], description: `Component '${componentName}' not found in registry` }; } catch (error) { console.error(`Error getting component properties: ${error}`); return { properties: [], events: [], methods: [], description: `Error: ${error instanceof Error ? error.message : String(error)}` }; } } async getComponentDetails(componentName, framework) { try { const propertiesAndEvents = await this.getComponentPropertiesAndEvents(componentName); const examples = await this.extractExamplesFromContent(this.reactKbPath, componentName, framework); return { component_name: componentName, framework: framework || 'react', ...propertiesAndEvents, examples }; } catch (error) { console.error(`Error getting component details: ${error}`); return { component_name: componentName, framework: framework || 'react', error: error instanceof Error ? error.message : String(error) }; } } async extractExamplesFromContent(kbPath, componentName, framework) { try { const content = await this.readTextFile(kbPath); return this.extractExamplesFromMarkdownContent(content, componentName, framework); } catch (error) { console.error(`Error extracting examples: ${error}`); return []; } } extractExamplesFromMarkdownContent(content, componentName, framework) { try { // Find the component section - exact match like Python version console.error(`Searching for ${componentName} examples in React KB`); console.error(`Looking for component: ${componentName} in markdown content`); // For Modus 2.0, component headers are directly with the component name const componentMarker = `# ${componentName}`; const startIndex = content.indexOf(componentMarker); if (startIndex === -1) { console.error(`Component ${componentName} not found in knowledge base`); return []; } console.error(`Found component marker: '${componentMarker}' at position ${startIndex}`); // Find the next component section or end of file // Look for next component header pattern: \n# ModusWc... (not just any # character) const nextComponentPattern = /\n# ModusWc\w+/; const nextComponentMatch = content.substring(startIndex + 1).match(nextComponentPattern); let componentSection; if (nextComponentMatch) { const nextComponentIndex = startIndex + 1 + nextComponentMatch.index; componentSection = content.substring(startIndex, nextComponentIndex); console.error(`Next component found at position: ${nextComponentIndex}`); } else { // If this is the last component in the file componentSection = content.substring(startIndex); console.error('This is the last component in file'); } console.error(`Component section length: ${componentSection.length} characters`); // Extract full prompt sections const examples = []; // Split the component section by prompt markers const promptSections = componentSection.split('## Prompt'); // Skip the first part which contains the component header if (promptSections.length > 1) { console.error(`Found ${promptSections.length - 1} prompt sections for ${componentName}`); for (let i = 1; i < promptSections.length; i++) { const section = promptSections[i]; const promptNumber = i; const fullContent = `## Prompt${section}`; // Extract code blocks for additional indexing - check multiple tsx patterns let code = ''; const codeMarkers = ['```tsx', '```jsx', '```typescript', '```javascript', '```']; for (const marker of codeMarkers) { const codeStart = section.indexOf(marker); if (codeStart !== -1) { const codeEnd = section.indexOf('```', codeStart + marker.length); if (codeEnd !== -1) { code = section.substring(codeStart + marker.length, codeEnd).trim(); break; } } } // Extract question for additional indexing let question = ''; const questionMarker = '**User Question:**'; const questionStart = section.indexOf(questionMarker); if (questionStart !== -1) { const answerMarker = '**Agent Answer:**'; const answerStart = section.indexOf(answerMarker, questionStart); if (answerStart !== -1) { question = section.substring(questionStart + questionMarker.length, answerStart).trim(); } } // Create a structured example object like Python version const example = { prompt_number: promptNumber, content: fullContent, question: question, code: code }; examples.push(example); } } else { console.error(`No prompt sections found for ${componentName}`); } return examples; } catch (error) { console.error(`Error extracting examples from markdown content: ${error}`); return []; } } async getInstallationGuidelines() { try { const guidelinesPath = path.join(this.basePath, 'knowledge-base', 'Modus 2', 'Modus2_guidelines.md'); return await this.readTextFile(guidelinesPath); } catch (error) { return `Error reading installation guidelines: ${error instanceof Error ? error.message : String(error)}`; } } async getKnowledgeBase() { try { const components = await this.getAllComponents(); const guidelines = await this.getInstallationGuidelines(); const icons = await this.getAllIconNames(); return { components, guidelines, icons: icons.slice(0, 50), // Limit icons for performance timestamp: new Date().toISOString() }; } catch (error) { return { error: `Error loading knowledge base: ${error instanceof Error ? error.message : String(error)}`, timestamp: new Date().toISOString() }; } } async getAllIconNames() { try { const data = await this.readJsonFile(this.iconsPath); return Array.isArray(data.icons) ? data.icons : []; } catch (error) { console.error(`Error getting icon names: ${error}`); return []; } } async getIconsByChar(charPrefix = '') { try { const allIcons = await this.getAllIconNames(); if (!charPrefix) { return allIcons.slice(0, 50); // Limit results } return allIcons .filter(icon => icon.toLowerCase().startsWith(charPrefix.toLowerCase())) .slice(0, 50); // Limit results } catch (error) { console.error(`Error filtering icons: ${error}`); return []; } } async readJsonFile(filePath) { const content = await this.readTextFile(filePath); return JSON.parse(content); } async readTextFile(filePath) { return new Promise((resolve, reject) => { fs.readFile(filePath, 'utf8', (err, data) => { if (err) { reject(err); } else { resolve(data); } }); }); } } //# sourceMappingURL=component-registry.js.map