@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
304 lines (289 loc) • 10.9 kB
JavaScript
;
/**
* Get Components Guide MCP Tool
*
* This tool retrieves comprehensive integration guides for specific components
* with intelligent path resolution and security validation.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getComponentsGuideSchema = void 0;
exports.getComponentsGuideTool = getComponentsGuideTool;
const zod_1 = require("zod");
const config_js_1 = require("../config.js");
const gitlabClient_js_1 = require("../services/gitlabClient.js");
/**
* Schema for get components guide tool parameters
*/
exports.getComponentsGuideSchema = zod_1.z.object({
path: zod_1.z.string().min(1).describe('Path to the component\'s llms.txt file (relative or absolute URL)')
});
/**
* Validates if a path is safe and doesn't contain dangerous characters
*/
function isValidPath(path) {
// Check for empty path
if (!path || path.trim().length === 0) {
return false;
}
// Check for dangerous characters and patterns
const dangerousPatterns = [
/\.\./, // Directory traversal
/<script/i, // Script injection
/<\/script/i, // Script injection
/javascript:/i, // JavaScript protocol
/data:/i, // Data protocol
/vbscript:/i, // VBScript protocol
/[<>]/, // HTML/XML tags
/[\x00-\x1f]/, // Control characters
];
return !dangerousPatterns.some(pattern => pattern.test(path));
}
/**
* Determines if a URL is external (not matching the internal GitLab host)
*/
function isExternalUrl(path, internalHost) {
// If path doesn't start with http/https, it's a relative path (internal)
if (!path.startsWith('http://') && !path.startsWith('https://')) {
return false;
}
// If no internal host is configured, treat all absolute URLs as external
if (!internalHost) {
return true;
}
try {
const url = new URL(path);
return url.hostname !== internalHost;
}
catch (e) {
// Malformed URL, treat as external to be safe
return true;
}
}
/**
* Extracts the title from content (first markdown header or filename)
*/
function extractTitle(content, path) {
// Try to find the first markdown header
const headerMatch = content.match(/^#\s+(.+)$/m);
if (headerMatch) {
return headerMatch[1].trim();
}
// Fall back to the filename or path
const filename = path.split('/').pop() || path;
return filename.replace(/\.(txt|md|markdown)$/i, '');
}
/**
* Formats the guide content with metadata
*/
function formatGuideContent(content, path) {
const title = extractTitle(content, path);
const contentLength = content.length;
return `# Component Guide: ${title}
**Source Path:** \`${path}\`
**Content Length:** ${contentLength} characters
**Retrieved:** ${new Date().toISOString()}
---
${content}`;
}
/**
* Fetches content from external URL using HTTP GET
*/
async function fetchExternalUrl(url) {
try {
console.log(`Fetching external URL: ${url}`);
const response = await fetch(url, {
headers: {
'User-Agent': 'awesome-components-mcp/1.0.0'
}
});
if (!response.ok) {
throw new gitlabClient_js_1.GitLabError(`Failed to fetch external URL (status: ${response.status})`, response.status, url);
}
const content = await response.text();
console.log(`Successfully fetched ${content.length} characters from external URL`);
return content;
}
catch (error) {
if (error instanceof gitlabClient_js_1.GitLabError) {
throw error;
}
throw new gitlabClient_js_1.GitLabError(`Network error while fetching external URL: ${error instanceof Error ? error.message : 'Unknown error'}`, undefined, url);
}
}
/**
* Validates GitLab MCP configuration
*/
function validateGitLabMcpConfig() {
if (!config_js_1.config.internalGuidesProjectId) {
return {
isValid: false,
error: 'GITLAB_PROJECT_ID is required for GitLab MCP instruction mode'
};
}
return { isValid: true };
}
/**
* Get Components Guide MCP Tool Implementation
*/
async function getComponentsGuideTool(params) {
try {
const { path } = params;
console.log(`Getting component guide for path: ${path}`);
// Validate the path for security
if (!isValidPath(path)) {
const errorResponse = `# Error: Invalid Component Guide Path
The provided path contains invalid characters or format: \`${path}\`
**Security Note:** Paths cannot contain:
- Directory traversal patterns (\`../\`)
- Script injection attempts (\`<script>\`)
- Control characters or HTML tags
- Protocol handlers (javascript:, data:, etc.)
**Valid Examples:**
- \`yeepay/dynamicpassword/llms.txt\`
- \`common/authentication/guide.md\`
- \`https://external-gitlab.com/project/-/raw/main/component/llms.txt\`
**Suggestions:**
- Use relative paths from the repository root
- Use absolute GitLab raw file URLs for external repositories
- Ensure the path points to a valid documentation file`;
return {
content: [{
type: 'text',
text: errorResponse
}],
isError: true
};
}
// Check if this is an external URL
if (isExternalUrl(path, config_js_1.config.internalGitlabHost)) {
console.log(`External URL detected: ${path}`);
// For external URLs, use direct HTTP GET request
const content = await fetchExternalUrl(path);
const formattedContent = formatGuideContent(content, path);
return {
content: [{
type: 'text',
text: formattedContent
}]
};
}
// For relative paths and internal URLs, generate gitlab-mcp instruction
console.log(`Relative/internal path detected, generating gitlab-mcp instruction: ${path}`);
// Validate GitLab MCP configuration
const configValidation = validateGitLabMcpConfig();
if (!configValidation.isValid) {
throw new Error(`GitLab MCP Configuration Error: ${configValidation.error}`);
}
// Derive file path for GitLab
let filePathForGitlab = path;
// If it's an internal URL, extract the path
if (path.startsWith('http://') || path.startsWith('https://')) {
try {
const url = new URL(path);
filePathForGitlab = url.pathname;
if (filePathForGitlab.startsWith('/')) {
filePathForGitlab = filePathForGitlab.substring(1);
}
}
catch (e) {
// If URL parsing fails, use the original path
}
}
// Generate JSON instruction for LLM to call gitlab-mcp
// project_id should be URL encoded only if it's a string (not a numeric ID)
// and only if it's not already encoded
let projectId = config_js_1.config.internalGuidesProjectId;
if (typeof projectId === 'string' && isNaN(Number(projectId))) {
// Check if it's already URL encoded by looking for % characters
if (!projectId.includes('%')) {
projectId = encodeURIComponent(projectId);
}
}
const instruction = {
action_type: 'mcp_call',
tool_name: 'get_file_contents',
parameters: {
project_id: projectId,
file_path: filePathForGitlab,
ref: 'main'
}
};
const instructionOutput = `# Component Guide - GitLab MCP Instruction
To retrieve the component guide for \`${path}\`, 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 component guide content
**Configuration:**
- Project ID: \`${config_js_1.config.internalGuidesProjectId}\`
- File Path: \`${filePathForGitlab}\`
- Branch: \`main\`
- Original Path: \`${path}\``;
return {
content: [{
type: 'text',
text: instructionOutput
}]
};
}
catch (error) {
console.error('Get components guide failed:', error);
let errorMessage = 'Unknown error occurred';
let statusInfo = '';
let suggestions = `**Suggestions:**
- Check if the component path is correct
- Verify the file exists in the repository
- Ensure you have proper access permissions
- Try using the components_discovery tool to find available components`;
if (error instanceof gitlabClient_js_1.GitLabError) {
errorMessage = `GitLab Error: ${error.message}`;
if (error.statusCode) {
statusInfo = `\n**Status Code:** ${error.statusCode}`;
// Provide specific suggestions based on status code
if (error.statusCode === 404) {
suggestions = `**Suggestions:**
- Verify the component path is correct: \`${params.path}\`
- Check if the file exists in the repository
- Use the components_discovery tool to see available components
- Ensure the file extension is correct (e.g., llms.txt, guide.md)`;
}
else if (error.statusCode === 403) {
suggestions = `**Suggestions:**
- This repository may be private and require authentication
- Check your GitLab Personal Access Token configuration
- Verify your token has read access to this repository
- Contact the repository administrator for access`;
}
}
if (error.url) {
statusInfo += `\n**URL:** ${error.url}`;
}
}
else if (error instanceof Error) {
errorMessage = `Error: ${error.message}`;
}
else {
errorMessage = `Error: ${String(error)}`;
}
const errorTitle = error instanceof gitlabClient_js_1.GitLabError && error.statusCode === 404
? 'Component Guide Not Found'
: 'Failed to Retrieve Component Guide';
const errorResponse = `# Error: ${errorTitle}
${errorMessage}${statusInfo}
${suggestions}
**Configuration:**
- GitLab Base URL: ${config_js_1.config.urls.gitlabBase}
- Authentication: ${config_js_1.config.auth.gitlabToken ? 'Configured' : 'Not configured'}
- Requested Path: \`${params.path}\``;
return {
content: [{
type: 'text',
text: errorResponse
}],
isError: true
};
}
}