@trimble-oss/modus-mcp-server
Version:
An MCP server providing information about Modus React form and UI components
244 lines • 10.7 kB
JavaScript
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