@yeepay/awesome-components-mcp
Version:
MCP server providing access to awesome-components documentation and integration guides with dual-mode operation: direct fetch and GitLab MCP instruction generation
221 lines (184 loc) • 6.49 kB
text/typescript
/**
* Components Discovery MCP Tool
*
* This tool discovers and categorizes all available components from the main llms.txt file.
* It supports multiple output formats: raw, parsed, and summary.
*/
import { z } from 'zod';
import { config } from '../config.js';
import { fetchGitLabFileContentSafe, GitLabError } from '../services/gitlabClient.js';
/**
* Schema for components discovery tool parameters
*/
export const componentsDiscoverySchema = z.object({});
export type ComponentsDiscoveryParams = z.infer<typeof componentsDiscoverySchema>;
/**
* Interface for parsed component information
*/
interface ParsedComponent {
name: string;
type: 'component' | 'guide' | 'documentation' | 'tutorial' | 'other';
path: string;
}
/**
* Interface for parsed components data
*/
interface ParsedComponentsData {
totalComponents: number;
componentsByType: Record<string, number>;
components: ParsedComponent[];
}
/**
* Parses llms.txt content and categorizes components
*/
function parseComponentsContent(content: string): ParsedComponentsData {
const lines = content.split('\n')
.map(line => line.trim())
.filter(line => line && !line.startsWith('#') && !line.startsWith('//'));
const components: ParsedComponent[] = lines.map(line => {
const name = line.split('/').pop() || line;
let type: ParsedComponent['type'] = 'other';
if (line.includes('/guide') || line.startsWith('guide/')) {
type = 'guide';
} else if (line.includes('/docs') || line.includes('/documentation') || line.endsWith('.md')) {
type = 'documentation';
} else if (line.includes('/tutorial') || line.startsWith('tutorial/')) {
type = 'tutorial';
} else if (!line.includes('/') || line.split('/').length <= 2) {
type = 'component';
}
return {
name,
type,
path: line
};
});
const componentsByType: Record<string, number> = {};
components.forEach(comp => {
componentsByType[comp.type] = (componentsByType[comp.type] || 0) + 1;
});
return {
totalComponents: components.length,
componentsByType,
components
};
}
/**
* Formats content as JSON data
*/
function formatOutput(content: string, sourceUrl?: string): string {
const source = sourceUrl || config.llmsTxtUrl || config.urls.mainLlms;
const parsed = parseComponentsContent(content);
// Always return JSON format
return JSON.stringify({
source,
totalComponents: parsed.totalComponents,
componentsByType: parsed.componentsByType,
components: parsed.components
}, null, 2);
}
/**
* Interface for gitlab-mcp instruction
*/
interface GitLabMcpInstruction {
action_type: 'mcp_call';
tool_name: string;
parameters: {
project_id: string;
file_path: string;
ref: string;
};
}
/**
* Components Discovery MCP Tool Implementation
*/
export async function componentsDiscoveryTool(): Promise<{ content: Array<{ type: 'text'; text: string }>; isError?: boolean }> {
try {
// Mode 1: Direct fetch mode (when LLMS_TXT_URL is configured)
if (config.llmsTxtUrl) {
console.log(`Fetching components list from: ${config.llmsTxtUrl}`);
// Fetch content from the configured URL
const content = await fetchGitLabFileContentSafe(config.llmsTxtUrl);
// Format the output as JSON
const formattedOutput = formatOutput(content, config.llmsTxtUrl);
return {
content: [{
type: 'text',
text: formattedOutput
}]
};
}
// Mode 2: gitlab-mcp instruction mode (when GITLAB_PROJECT_ID and LLMS_TXT_FILE are configured)
if (config.llmsTxtProjectId) {
console.log(`Generating gitlab-mcp instruction for project: ${config.llmsTxtProjectId}, file: ${config.llmsTxtFile}`);
// Generate JSON instruction for LLM to call gitlab-mcp
const instruction: GitLabMcpInstruction = {
action_type: 'mcp_call',
tool_name: 'get_file_contents',
parameters: {
project_id: config.llmsTxtProjectId, // Already URL-encoded as per PRD
file_path: config.llmsTxtFile,
ref: 'main' // Default branch
}
};
const instructionOutput = `# Components Discovery - GitLab MCP Instruction
To retrieve the components list, please call the gitlab-mcp server with the following instruction:
\`\`\`json
${JSON.stringify(instruction, null, 2)}
\`\`\`
**Instructions for LLM:**
1. Use the above JSON instruction to call the gitlab-mcp server
2. Call the \`get_file_contents\` tool with the provided parameters
3. The response will contain the components list (llms.txt content)
**Configuration:**
- Project ID: \`${config.llmsTxtProjectId}\`
- File Path: \`${config.llmsTxtFile}\`
- Branch: \`main\``;
return {
content: [{
type: 'text',
text: instructionOutput
}]
};
}
// Configuration error: neither mode is properly configured
throw new Error('Configuration error: Either LLMS_TXT_URL or both GITLAB_PROJECT_ID and LLMS_TXT_FILE must be configured');
} catch (error) {
console.error('Components discovery failed:', error);
let errorMessage = 'Unknown error occurred';
let statusInfo = '';
if (error instanceof GitLabError) {
errorMessage = `GitLab Error: ${error.message}`;
if (error.statusCode) {
statusInfo = `\n**Status Code:** ${error.statusCode}`;
}
if (error.url) {
statusInfo += `\n**URL:** ${error.url}`;
}
} else if (error instanceof Error) {
errorMessage = `Error: ${error.message}`;
} else {
errorMessage = `Error: ${String(error)}`;
}
const errorResponse = `# Error: Components Discovery Failed
${errorMessage}${statusInfo}
**Suggestions:**
- Check your network connection
- Verify the GitLab URL is accessible: ${config.llmsTxtUrl || config.urls.mainLlms}
- Ensure you have proper authentication if the repository is private
- Check if the llms.txt file exists at the specified location
**Configuration:**
- Mode: ${config.llmsTxtUrl ? 'Direct fetch' : 'GitLab MCP instruction'}
- LLMS_TXT_URL: ${config.llmsTxtUrl || 'Not configured'}
- GITLAB_PROJECT_ID: ${config.llmsTxtProjectId || 'Not configured'}
- LLMS_TXT_FILE: ${config.llmsTxtFile || 'Not configured'}
- Authentication: ${config.auth.gitlabToken ? 'Configured' : 'Not configured'}`;
return {
content: [{
type: 'text',
text: errorResponse
}],
isError: true
};
}
}